diff --git a/src/poly/fs.cc b/src/poly/fs.cc new file mode 100644 index 000000000..8ae83a373 --- /dev/null +++ b/src/poly/fs.cc @@ -0,0 +1,194 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "poly/fs.h" + +#include +#include +#include + +namespace poly { +namespace fs { + +std::string CanonicalizePath(const std::string& original_path) { + char path_separator('\\'); + std::string path(poly::fix_path_separators(original_path, path_separator)); + + std::vector path_breaks; + + std::string::size_type pos(path.find_first_of(path_separator)); + std::string::size_type pos_n(std::string::npos); + + while (pos != std::string::npos) { + if ((pos_n = path.find_first_of(path_separator, pos + 1)) == std::string::npos) { + pos_n = path.size(); + } + + auto diff(pos_n - pos); + switch (diff) + { + case 0: + pos_n = std::string::npos; + break; + case 1: + // Duplicate separators + path.erase(pos, 1); + pos_n -= 1; + break; + case 2: + // Potential marker for current directory + if (path[pos + 1] == '.') { + path.erase(pos, 2); + pos_n -= 2; + } + else { + path_breaks.push_back(pos); + } + break; + case 3: + // Potential marker for parent directory + if (path[pos + 1] == '.' && path[pos + 2] == '.'){ + if (path_breaks.empty()) { + // Ensure we don't override the device name + std::string::size_type loc(path.find_first_of(':')); + auto req(pos + 3); + if (loc == std::string::npos || loc > req) { + path.erase(0, req); + pos_n -= req; + } + else { + path.erase(loc + 1, req - (loc + 1)); + pos_n -= req - (loc + 1); + } + } + else { + auto last(path_breaks.back()); + auto last_diff((pos + 3) - last); + path.erase(last, last_diff); + pos_n = last; + // Also remove path reference + path_breaks.erase(path_breaks.end() - 1); + } + } + else { + path_breaks.push_back(pos); + } + break; + + default: + path_breaks.push_back(pos); + break; + } + + pos = pos_n; + } + + // Remove trailing seperator + if (!path.empty() && path.back() == path_separator) { + path.erase(path.size() - 1); + } + + // Final sanity check for dead paths + if ((path.size() == 1 && (path[0] == '.' || path[0] == path_separator)) + || (path.size() == 2 && path[0] == '.' && path[1] == '.')) { + return ""; + } + + return path; +} + +WildcardFlags WildcardFlags::FIRST(true, false); +WildcardFlags WildcardFlags::LAST(false, true); + +WildcardFlags::WildcardFlags() + : FromStart(false) + , ToEnd(false) + , WithCase(false) +{ } + +WildcardFlags::WildcardFlags(bool start, bool end) + : FromStart(start) + , ToEnd(end) + , WithCase(false) +{ } + +WildcardRule::WildcardRule(std::string str_match, const WildcardFlags &flags) + : match(str_match) + , rules(flags) +{ } + +bool WildcardRule::Check(std::string& str) const +{ + if( match.empty() ) { + return true; + } + + if( str.size() < match.size() ) { + return false; + } + + std::string::size_type result(str.find(match)); + + if( result != std::string::npos ) { + if( rules.FromStart && result != 0 ) { + return false; + } + + if( rules.ToEnd && result != ( ( str.size() -1 ) - match.size() ) ) { + return false; + } + + str.erase(0, result + match.size()); + return true; + } + + return false; +} + +void WildcardEngine::PreparePattern(const std::string &pattern) +{ + rules.clear(); + + WildcardFlags flags(WildcardFlags::FIRST); + size_t n = 0; + size_t last = 0; + while ((n = pattern.find_first_of('*', last)) != pattern.npos) { + if (last != n) { + std::string str_str(pattern.substr(last, n - last)); + rules.push_back(WildcardRule(str_str, flags)); + } + last = n + 1; + flags = WildcardFlags(); + } + if (last != pattern.size()) { + std::string str_str(pattern.substr(last)); + rules.push_back(WildcardRule(str_str, WildcardFlags::LAST)); + } +} + +void WildcardEngine::SetRule(const std::string &pattern) +{ + PreparePattern(pattern); +} + +bool WildcardEngine::Match(const std::string &str) const +{ + std::string str_copy(str); + + for (const auto& rule : rules) { + if (!(rule.Check(str_copy))) { + return false; + } + } + + return true; +} + +} // namespace fs +} // namespace poly diff --git a/src/poly/fs.h b/src/poly/fs.h index f6bd91dc1..e6c73c50b 100644 --- a/src/poly/fs.h +++ b/src/poly/fs.h @@ -15,6 +15,9 @@ #include "poly/config.h" #include "poly/string.h" +#include +#include + namespace poly { namespace fs { @@ -35,6 +38,44 @@ struct FileInfo { }; std::vector ListFiles(const std::wstring& path); +std::string CanonicalizePath(const std::string& original_path); + +class WildcardFlags +{ +public: + bool + FromStart: 1, + ToEnd : 1, + WithCase : 1; // Unused for now + + WildcardFlags(); + WildcardFlags(bool start, bool end); + + static WildcardFlags FIRST; + static WildcardFlags LAST; +}; + +class WildcardRule +{ +public: + WildcardRule(std::string str_match, const WildcardFlags &flags); + bool Check(std::string& str) const; + +private: + std::string match; + WildcardFlags rules; +}; + +class WildcardEngine +{ +public: + void SetRule(const std::string &pattern); + bool Match(const std::string &str) const; +private: + std::vector rules; + void PreparePattern(const std::string &pattern); +}; + } // namespace fs } // namespace poly diff --git a/src/poly/sources.gypi b/src/poly/sources.gypi index 2d8f6f320..031932879 100644 --- a/src/poly/sources.gypi +++ b/src/poly/sources.gypi @@ -9,6 +9,7 @@ 'config.h', 'cxx_compat.h', 'fs.h', + 'fs.cc', 'logging.cc', 'logging.h', 'main.h', diff --git a/src/xenia/kernel/fs/devices/disc_image_entry.cc b/src/xenia/kernel/fs/devices/disc_image_entry.cc index 707d1d82b..ea9eef8bf 100644 --- a/src/xenia/kernel/fs/devices/disc_image_entry.cc +++ b/src/xenia/kernel/fs/devices/disc_image_entry.cc @@ -11,7 +11,6 @@ #include -#include "xenia/kernel/fs/gdfx.h" #include "xenia/kernel/fs/devices/disc_image_file.h" namespace xe { @@ -31,7 +30,7 @@ DiscImageEntry::DiscImageEntry(Device* device, const char* path, : Entry(device, path), mmap_(mmap), gdfx_entry_(gdfx_entry), - gdfx_entry_iterator_(gdfx_entry->children.end()) {} + it_(gdfx_entry->children.end()) {} DiscImageEntry::~DiscImageEntry() {} @@ -51,39 +50,34 @@ X_STATUS DiscImageEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length, const char* file_name, bool restart) { assert_not_null(out_info); - // TODO(benvanik): move to common code. - assert_null(file_name); - GDFXEntry* entry = nullptr; - if (file_name) { - // Specified filename, return just that info. - assert_true(std::strchr(file_name, '*') == nullptr); - entry = gdfx_entry_->GetChild(file_name); + GDFXEntry* entry(nullptr); + + if (file_name != nullptr) { + // Only queries in the current directory are supported for now + assert_true(std::strchr(file_name, '\\') == nullptr); + + find_engine_.SetRule(file_name); + + // Always restart the search? + it_ = gdfx_entry_->children.begin(); + entry = gdfx_entry_->GetChild(find_engine_, it_); if (!entry) { return X_STATUS_NO_SUCH_FILE; } - } else { - if (restart == true && - gdfx_entry_iterator_ != gdfx_entry_->children.end()) { - gdfx_entry_iterator_ = gdfx_entry_->children.end(); + } + else { + if (restart) { + it_ = gdfx_entry_->children.begin(); } - if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) { - gdfx_entry_iterator_ = gdfx_entry_->children.begin(); - if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) { - return X_STATUS_UNSUCCESSFUL; - } - } else { - ++gdfx_entry_iterator_; - if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) { - return X_STATUS_UNSUCCESSFUL; - } + entry = gdfx_entry_->GetChild(find_engine_, it_); + if (!entry) { + return X_STATUS_UNSUCCESSFUL; } auto end = (uint8_t*)out_info + length; - entry = *gdfx_entry_iterator_; auto entry_name = entry->name; if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) { - gdfx_entry_iterator_ = gdfx_entry_->children.end(); return X_STATUS_NO_MORE_FILES; } } @@ -96,7 +90,7 @@ X_STATUS DiscImageEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length, out_info->change_time = 0; out_info->end_of_file = entry->size; out_info->allocation_size = 2048; - out_info->attributes = (X_FILE_ATTRIBUTES)entry->attributes; + out_info->attributes = entry->attributes; out_info->file_name_length = static_cast(entry->name.size()); memcpy(out_info->file_name, entry->name.c_str(), entry->name.size()); diff --git a/src/xenia/kernel/fs/devices/disc_image_entry.h b/src/xenia/kernel/fs/devices/disc_image_entry.h index 4fed87d15..ece0afa65 100644 --- a/src/xenia/kernel/fs/devices/disc_image_entry.h +++ b/src/xenia/kernel/fs/devices/disc_image_entry.h @@ -15,13 +15,13 @@ #include "poly/mapped_memory.h" #include "xenia/common.h" #include "xenia/kernel/fs/entry.h" +#include "poly/fs.h" +#include "xenia/kernel/fs/gdfx.h" namespace xe { namespace kernel { namespace fs { -class GDFXEntry; - class DiscImageEntry : public Entry { public: DiscImageEntry(Device* device, const char* path, poly::MappedMemory* mmap, @@ -45,7 +45,9 @@ class DiscImageEntry : public Entry { private: poly::MappedMemory* mmap_; GDFXEntry* gdfx_entry_; - std::vector::iterator gdfx_entry_iterator_; + + poly::fs::WildcardEngine find_engine_; + GDFXEntry::child_it_t it_; }; } // namespace fs diff --git a/src/xenia/kernel/fs/devices/stfs_container_entry.cc b/src/xenia/kernel/fs/devices/stfs_container_entry.cc index 1119933a2..760d6d93e 100644 --- a/src/xenia/kernel/fs/devices/stfs_container_entry.cc +++ b/src/xenia/kernel/fs/devices/stfs_container_entry.cc @@ -9,7 +9,6 @@ #include "xenia/kernel/fs/devices/stfs_container_entry.h" -#include "xenia/kernel/fs/stfs.h" #include "xenia/kernel/fs/devices/stfs_container_file.h" namespace xe { @@ -22,7 +21,8 @@ STFSContainerEntry::STFSContainerEntry(Device* device, const char* path, : Entry(device, path), mmap_(mmap), stfs_entry_(stfs_entry), - stfs_entry_iterator_(stfs_entry->children.end()) {} + it_(stfs_entry_->children.end()) +{ } STFSContainerEntry::~STFSContainerEntry() = default; @@ -44,37 +44,33 @@ X_STATUS STFSContainerEntry::QueryDirectory(XDirectoryInfo* out_info, bool restart) { assert_not_null(out_info); - // TODO(benvanik): move to common code. - STFSEntry* entry = nullptr; - if (file_name) { - // Specified filename, return just that info. - assert_true(std::strchr(file_name, '*') == nullptr); - entry = stfs_entry_->GetChild(file_name); + STFSEntry* entry(nullptr); + + if( file_name != nullptr ) { + // Only queries in the current directory are supported for now + assert_true(std::strchr(file_name, '\\') == nullptr); + + find_engine_.SetRule(file_name); + + // Always restart the search? + it_ = stfs_entry_->children.begin(); + entry = stfs_entry_->GetChild(find_engine_, it_); if (!entry) { return X_STATUS_NO_SUCH_FILE; } } else { - if (restart && stfs_entry_iterator_ != stfs_entry_->children.end()) { - stfs_entry_iterator_ = stfs_entry_->children.end(); + if (restart) { + it_ = stfs_entry_->children.begin(); } - if (stfs_entry_iterator_ == stfs_entry_->children.end()) { - stfs_entry_iterator_ = stfs_entry_->children.begin(); - if (stfs_entry_iterator_ == stfs_entry_->children.end()) { - return X_STATUS_UNSUCCESSFUL; - } - } else { - ++stfs_entry_iterator_; - if (stfs_entry_iterator_ == stfs_entry_->children.end()) { - return X_STATUS_UNSUCCESSFUL; - } + entry = stfs_entry_->GetChild(find_engine_, it_); + if (!entry) { + return X_STATUS_UNSUCCESSFUL; } auto end = (uint8_t*)out_info + length; - entry = stfs_entry_iterator_->get(); auto entry_name = entry->name; if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) { - stfs_entry_iterator_ = stfs_entry_->children.end(); return X_STATUS_NO_MORE_FILES; } } diff --git a/src/xenia/kernel/fs/devices/stfs_container_entry.h b/src/xenia/kernel/fs/devices/stfs_container_entry.h index 4b83866cd..b4de95570 100644 --- a/src/xenia/kernel/fs/devices/stfs_container_entry.h +++ b/src/xenia/kernel/fs/devices/stfs_container_entry.h @@ -11,17 +11,18 @@ #define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_ #include +#include #include "poly/mapped_memory.h" #include "xenia/common.h" #include "xenia/kernel/fs/entry.h" +#include "poly/fs.h" +#include "xenia/kernel/fs/stfs.h" namespace xe { namespace kernel { namespace fs { -class STFSEntry; - class STFSContainerEntry : public Entry { public: STFSContainerEntry(Device* device, const char* path, poly::MappedMemory* mmap, @@ -41,7 +42,9 @@ class STFSContainerEntry : public Entry { private: poly::MappedMemory* mmap_; STFSEntry* stfs_entry_; - std::vector>::iterator stfs_entry_iterator_; + + poly::fs::WildcardEngine find_engine_; + STFSEntry::child_it_t it_; }; } // namespace fs diff --git a/src/xenia/kernel/fs/filesystem.cc b/src/xenia/kernel/fs/filesystem.cc index c2ce3d00e..9ea573c4f 100644 --- a/src/xenia/kernel/fs/filesystem.cc +++ b/src/xenia/kernel/fs/filesystem.cc @@ -13,6 +13,7 @@ #include "xenia/kernel/fs/devices/disc_image_device.h" #include "xenia/kernel/fs/devices/host_path_device.h" #include "xenia/kernel/fs/devices/stfs_container_device.h" +#include "poly/fs.h" namespace xe { namespace kernel { @@ -147,92 +148,9 @@ int FileSystem::DeleteSymbolicLink(const std::string& path) { return 0; } -std::string FileSystem::CanonicalizePath(const std::string& original_path) const { - char path_separator('\\'); - std::string path(poly::fix_path_separators(original_path, path_separator)); - - std::vector path_breaks; - - std::string::size_type pos(path.find_first_of(path_separator)); - std::string::size_type pos_n(std::string::npos); - - while (pos != std::string::npos) { - if ((pos_n = path.find_first_of(path_separator, pos + 1)) == std::string::npos) { - pos_n = path.size(); - } - - auto diff(pos_n - pos); - switch (diff) - { - case 0: - pos_n = std::string::npos; - break; - case 1: - // Duplicate separators - path.erase(pos, 1); - pos_n -= 1; - break; - case 2: - // Potential marker for current directory - if (path[pos + 1] == '.') { - path.erase(pos, 2); - pos_n -= 2; - } else { - path_breaks.push_back(pos); - } - break; - case 3: - // Potential marker for parent directory - if (path[pos + 1] == '.' && path[pos + 2] == '.'){ - if (path_breaks.empty()) { - // Ensure we don't override the device name - std::string::size_type loc(path.find_first_of(':')); - auto req(pos + 3); - if (loc == std::string::npos || loc > req) { - path.erase(0, req); - pos_n -= req; - } else { - path.erase(loc + 1, req - (loc + 1)); - pos_n -= req - (loc + 1); - } - } else { - auto last(path_breaks.back()); - auto last_diff((pos + 3) - last); - path.erase(last, last_diff); - pos_n = last; - // Also remove path reference - path_breaks.erase(path_breaks.end() - 1); - } - } else { - path_breaks.push_back(pos); - } - break; - - default: - path_breaks.push_back(pos); - break; - } - - pos = pos_n; - } - - // Remove trailing seperator - if (!path.empty() && path.back() == path_separator) { - path.erase(path.size() - 1); - } - - // Final sanity check for dead paths - if ((path.size() == 1 && (path[0] == '.' || path[0] == path_separator)) - || (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)); + std::string normalized_path(poly::fs::CanonicalizePath(path)); // If no path (starts with a slash) do it module-relative. // Which for now, we just make game:. diff --git a/src/xenia/kernel/fs/filesystem.h b/src/xenia/kernel/fs/filesystem.h index b434e6b60..6c87e6455 100644 --- a/src/xenia/kernel/fs/filesystem.h +++ b/src/xenia/kernel/fs/filesystem.h @@ -57,8 +57,6 @@ class FileSystem { private: std::vector devices_; std::unordered_map symlinks_; - - std::string CanonicalizePath(const std::string& original_path) const; }; } // namespace fs diff --git a/src/xenia/kernel/fs/gdfx.cc b/src/xenia/kernel/fs/gdfx.cc index 956044374..38fa73195 100644 --- a/src/xenia/kernel/fs/gdfx.cc +++ b/src/xenia/kernel/fs/gdfx.cc @@ -29,6 +29,20 @@ GDFXEntry::~GDFXEntry() { } } +GDFXEntry* GDFXEntry::GetChild(const poly::fs::WildcardEngine& engine, child_it_t& ref_it) +{ + GDFXEntry* child_entry(nullptr); + while (ref_it != children.end()) { + if (engine.Match((*ref_it)->name)) { + child_entry = (*ref_it); + ++ref_it; + break; + } + ++ref_it; + } + return child_entry; +} + GDFXEntry* GDFXEntry::GetChild(const char* name) { // TODO(benvanik): a faster search for (std::vector::iterator it = children.begin(); diff --git a/src/xenia/kernel/fs/gdfx.h b/src/xenia/kernel/fs/gdfx.h index 7fe2817a1..2caedd780 100644 --- a/src/xenia/kernel/fs/gdfx.h +++ b/src/xenia/kernel/fs/gdfx.h @@ -16,6 +16,7 @@ #include "xenia/common.h" #include "xenia/xbox.h" #include "xenia/kernel/fs/entry.h" +#include "poly/fs.h" namespace xe { namespace kernel { @@ -28,6 +29,10 @@ class GDFXEntry { GDFXEntry(); ~GDFXEntry(); + typedef std::vector child_t; + typedef child_t::iterator child_it_t; + + GDFXEntry* GetChild(const poly::fs::WildcardEngine& engine, child_it_t& ref_it); GDFXEntry* GetChild(const char* name); void Dump(int indent); @@ -36,8 +41,7 @@ class GDFXEntry { X_FILE_ATTRIBUTES attributes; size_t offset; size_t size; - - std::vector children; + child_t children; }; class GDFX { diff --git a/src/xenia/kernel/fs/stfs.cc b/src/xenia/kernel/fs/stfs.cc index 036703a52..f7593b2eb 100644 --- a/src/xenia/kernel/fs/stfs.cc +++ b/src/xenia/kernel/fs/stfs.cc @@ -100,6 +100,20 @@ STFSEntry::STFSEntry() update_timestamp(0), access_timestamp(0) {} +STFSEntry* STFSEntry::GetChild(const poly::fs::WildcardEngine& engine, child_it_t& ref_it) +{ + STFSEntry* child_entry(nullptr); + while (ref_it != children.end()) { + if (engine.Match((*ref_it)->name)) { + child_entry = (*ref_it).get(); + ++ref_it; + break; + } + ++ref_it; + } + return child_entry; +} + STFSEntry* STFSEntry::GetChild(const char* name) { // TODO(benvanik): a faster search for (const auto& entry : children) { diff --git a/src/xenia/kernel/fs/stfs.h b/src/xenia/kernel/fs/stfs.h index 03d13bbff..a3d9f48a2 100644 --- a/src/xenia/kernel/fs/stfs.h +++ b/src/xenia/kernel/fs/stfs.h @@ -17,6 +17,7 @@ #include "xenia/common.h" #include "xenia/xbox.h" #include "xenia/kernel/fs/entry.h" +#include "poly/fs.h" namespace xe { namespace kernel { @@ -131,6 +132,10 @@ class STFSEntry { public: STFSEntry(); + typedef std::vector> child_t; + typedef child_t::iterator child_it_t; + + STFSEntry* GetChild(const poly::fs::WildcardEngine& engine, child_it_t& ref_it); STFSEntry* GetChild(const char* name); void Dump(int indent); @@ -141,8 +146,7 @@ class STFSEntry { size_t size; uint32_t update_timestamp; uint32_t access_timestamp; - - std::vector> children; + child_t children; typedef struct { size_t offset;