Support for wildcard queries

Provides support for persistent wildcard file requests, as described in
#96
Also moved CanonicalizePath into common code (poly::fs)
This commit is contained in:
x1nixmzeng 2015-02-13 23:41:19 +00:00
parent 3d980dd294
commit 4f7761c5e2
13 changed files with 327 additions and 144 deletions

194
src/poly/fs.cc Normal file
View File

@ -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 <string>
#include <vector>
#include <algorithm>
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<std::string::size_type> 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

View File

@ -15,6 +15,9 @@
#include "poly/config.h" #include "poly/config.h"
#include "poly/string.h" #include "poly/string.h"
#include <vector>
#include <iterator>
namespace poly { namespace poly {
namespace fs { namespace fs {
@ -35,6 +38,44 @@ struct FileInfo {
}; };
std::vector<FileInfo> ListFiles(const std::wstring& path); std::vector<FileInfo> 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<WildcardRule> rules;
void PreparePattern(const std::string &pattern);
};
} // namespace fs } // namespace fs
} // namespace poly } // namespace poly

View File

@ -9,6 +9,7 @@
'config.h', 'config.h',
'cxx_compat.h', 'cxx_compat.h',
'fs.h', 'fs.h',
'fs.cc',
'logging.cc', 'logging.cc',
'logging.h', 'logging.h',
'main.h', 'main.h',

View File

@ -11,7 +11,6 @@
#include <algorithm> #include <algorithm>
#include "xenia/kernel/fs/gdfx.h"
#include "xenia/kernel/fs/devices/disc_image_file.h" #include "xenia/kernel/fs/devices/disc_image_file.h"
namespace xe { namespace xe {
@ -31,7 +30,7 @@ DiscImageEntry::DiscImageEntry(Device* device, const char* path,
: Entry(device, path), : Entry(device, path),
mmap_(mmap), mmap_(mmap),
gdfx_entry_(gdfx_entry), gdfx_entry_(gdfx_entry),
gdfx_entry_iterator_(gdfx_entry->children.end()) {} it_(gdfx_entry->children.end()) {}
DiscImageEntry::~DiscImageEntry() {} DiscImageEntry::~DiscImageEntry() {}
@ -51,39 +50,34 @@ X_STATUS DiscImageEntry::QueryDirectory(XDirectoryInfo* out_info, size_t length,
const char* file_name, bool restart) { const char* file_name, bool restart) {
assert_not_null(out_info); assert_not_null(out_info);
// TODO(benvanik): move to common code. GDFXEntry* entry(nullptr);
assert_null(file_name);
GDFXEntry* entry = nullptr; if (file_name != nullptr) {
if (file_name) { // Only queries in the current directory are supported for now
// Specified filename, return just that info. assert_true(std::strchr(file_name, '\\') == nullptr);
assert_true(std::strchr(file_name, '*') == nullptr);
entry = gdfx_entry_->GetChild(file_name); find_engine_.SetRule(file_name);
// Always restart the search?
it_ = gdfx_entry_->children.begin();
entry = gdfx_entry_->GetChild(find_engine_, it_);
if (!entry) { if (!entry) {
return X_STATUS_NO_SUCH_FILE; return X_STATUS_NO_SUCH_FILE;
} }
} else { }
if (restart == true && else {
gdfx_entry_iterator_ != gdfx_entry_->children.end()) { if (restart) {
gdfx_entry_iterator_ = gdfx_entry_->children.end(); it_ = gdfx_entry_->children.begin();
} }
if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) { entry = gdfx_entry_->GetChild(find_engine_, it_);
gdfx_entry_iterator_ = gdfx_entry_->children.begin(); if (!entry) {
if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) {
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
} else {
++gdfx_entry_iterator_;
if (gdfx_entry_iterator_ == gdfx_entry_->children.end()) {
return X_STATUS_UNSUCCESSFUL;
}
}
auto end = (uint8_t*)out_info + length; auto end = (uint8_t*)out_info + length;
entry = *gdfx_entry_iterator_;
auto entry_name = entry->name; auto entry_name = entry->name;
if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) { 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; 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->change_time = 0;
out_info->end_of_file = entry->size; out_info->end_of_file = entry->size;
out_info->allocation_size = 2048; 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<uint32_t>(entry->name.size()); out_info->file_name_length = static_cast<uint32_t>(entry->name.size());
memcpy(out_info->file_name, entry->name.c_str(), entry->name.size()); memcpy(out_info->file_name, entry->name.c_str(), entry->name.size());

View File

@ -15,13 +15,13 @@
#include "poly/mapped_memory.h" #include "poly/mapped_memory.h"
#include "xenia/common.h" #include "xenia/common.h"
#include "xenia/kernel/fs/entry.h" #include "xenia/kernel/fs/entry.h"
#include "poly/fs.h"
#include "xenia/kernel/fs/gdfx.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace fs { namespace fs {
class GDFXEntry;
class DiscImageEntry : public Entry { class DiscImageEntry : public Entry {
public: public:
DiscImageEntry(Device* device, const char* path, poly::MappedMemory* mmap, DiscImageEntry(Device* device, const char* path, poly::MappedMemory* mmap,
@ -45,7 +45,9 @@ class DiscImageEntry : public Entry {
private: private:
poly::MappedMemory* mmap_; poly::MappedMemory* mmap_;
GDFXEntry* gdfx_entry_; GDFXEntry* gdfx_entry_;
std::vector<GDFXEntry*>::iterator gdfx_entry_iterator_;
poly::fs::WildcardEngine find_engine_;
GDFXEntry::child_it_t it_;
}; };
} // namespace fs } // namespace fs

View File

@ -9,7 +9,6 @@
#include "xenia/kernel/fs/devices/stfs_container_entry.h" #include "xenia/kernel/fs/devices/stfs_container_entry.h"
#include "xenia/kernel/fs/stfs.h"
#include "xenia/kernel/fs/devices/stfs_container_file.h" #include "xenia/kernel/fs/devices/stfs_container_file.h"
namespace xe { namespace xe {
@ -22,7 +21,8 @@ STFSContainerEntry::STFSContainerEntry(Device* device, const char* path,
: Entry(device, path), : Entry(device, path),
mmap_(mmap), mmap_(mmap),
stfs_entry_(stfs_entry), stfs_entry_(stfs_entry),
stfs_entry_iterator_(stfs_entry->children.end()) {} it_(stfs_entry_->children.end())
{ }
STFSContainerEntry::~STFSContainerEntry() = default; STFSContainerEntry::~STFSContainerEntry() = default;
@ -44,37 +44,33 @@ X_STATUS STFSContainerEntry::QueryDirectory(XDirectoryInfo* out_info,
bool restart) { bool restart) {
assert_not_null(out_info); assert_not_null(out_info);
// TODO(benvanik): move to common code. STFSEntry* entry(nullptr);
STFSEntry* entry = nullptr;
if (file_name) { if( file_name != nullptr ) {
// Specified filename, return just that info. // Only queries in the current directory are supported for now
assert_true(std::strchr(file_name, '*') == nullptr); assert_true(std::strchr(file_name, '\\') == nullptr);
entry = stfs_entry_->GetChild(file_name);
find_engine_.SetRule(file_name);
// Always restart the search?
it_ = stfs_entry_->children.begin();
entry = stfs_entry_->GetChild(find_engine_, it_);
if (!entry) { if (!entry) {
return X_STATUS_NO_SUCH_FILE; return X_STATUS_NO_SUCH_FILE;
} }
} else { } else {
if (restart && stfs_entry_iterator_ != stfs_entry_->children.end()) { if (restart) {
stfs_entry_iterator_ = stfs_entry_->children.end(); it_ = stfs_entry_->children.begin();
} }
if (stfs_entry_iterator_ == stfs_entry_->children.end()) { entry = stfs_entry_->GetChild(find_engine_, it_);
stfs_entry_iterator_ = stfs_entry_->children.begin(); if (!entry) {
if (stfs_entry_iterator_ == stfs_entry_->children.end()) {
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
} else {
++stfs_entry_iterator_;
if (stfs_entry_iterator_ == stfs_entry_->children.end()) {
return X_STATUS_UNSUCCESSFUL;
}
}
auto end = (uint8_t*)out_info + length; auto end = (uint8_t*)out_info + length;
entry = stfs_entry_iterator_->get();
auto entry_name = entry->name; auto entry_name = entry->name;
if (((uint8_t*)&out_info->file_name[0]) + entry_name.size() > end) { 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; return X_STATUS_NO_MORE_FILES;
} }
} }

View File

@ -11,17 +11,18 @@
#define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_ #define XENIA_KERNEL_FS_DEVICES_STFS_CONTAINER_ENTRY_H_
#include <vector> #include <vector>
#include <iterator>
#include "poly/mapped_memory.h" #include "poly/mapped_memory.h"
#include "xenia/common.h" #include "xenia/common.h"
#include "xenia/kernel/fs/entry.h" #include "xenia/kernel/fs/entry.h"
#include "poly/fs.h"
#include "xenia/kernel/fs/stfs.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace fs { namespace fs {
class STFSEntry;
class STFSContainerEntry : public Entry { class STFSContainerEntry : public Entry {
public: public:
STFSContainerEntry(Device* device, const char* path, poly::MappedMemory* mmap, STFSContainerEntry(Device* device, const char* path, poly::MappedMemory* mmap,
@ -41,7 +42,9 @@ class STFSContainerEntry : public Entry {
private: private:
poly::MappedMemory* mmap_; poly::MappedMemory* mmap_;
STFSEntry* stfs_entry_; STFSEntry* stfs_entry_;
std::vector<std::unique_ptr<STFSEntry>>::iterator stfs_entry_iterator_;
poly::fs::WildcardEngine find_engine_;
STFSEntry::child_it_t it_;
}; };
} // namespace fs } // namespace fs

View File

@ -13,6 +13,7 @@
#include "xenia/kernel/fs/devices/disc_image_device.h" #include "xenia/kernel/fs/devices/disc_image_device.h"
#include "xenia/kernel/fs/devices/host_path_device.h" #include "xenia/kernel/fs/devices/host_path_device.h"
#include "xenia/kernel/fs/devices/stfs_container_device.h" #include "xenia/kernel/fs/devices/stfs_container_device.h"
#include "poly/fs.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
@ -147,92 +148,9 @@ int FileSystem::DeleteSymbolicLink(const std::string& path) {
return 0; 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<std::string::size_type> 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<Entry> FileSystem::ResolvePath(const std::string& path) { std::unique_ptr<Entry> FileSystem::ResolvePath(const std::string& path) {
// Resolve relative paths // 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. // If no path (starts with a slash) do it module-relative.
// Which for now, we just make game:. // Which for now, we just make game:.

View File

@ -57,8 +57,6 @@ class FileSystem {
private: private:
std::vector<Device*> devices_; std::vector<Device*> devices_;
std::unordered_map<std::string, std::string> symlinks_; std::unordered_map<std::string, std::string> symlinks_;
std::string CanonicalizePath(const std::string& original_path) const;
}; };
} // namespace fs } // namespace fs

View File

@ -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) { GDFXEntry* GDFXEntry::GetChild(const char* name) {
// TODO(benvanik): a faster search // TODO(benvanik): a faster search
for (std::vector<GDFXEntry*>::iterator it = children.begin(); for (std::vector<GDFXEntry*>::iterator it = children.begin();

View File

@ -16,6 +16,7 @@
#include "xenia/common.h" #include "xenia/common.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
#include "xenia/kernel/fs/entry.h" #include "xenia/kernel/fs/entry.h"
#include "poly/fs.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
@ -28,6 +29,10 @@ class GDFXEntry {
GDFXEntry(); GDFXEntry();
~GDFXEntry(); ~GDFXEntry();
typedef std::vector<GDFXEntry*> 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); GDFXEntry* GetChild(const char* name);
void Dump(int indent); void Dump(int indent);
@ -36,8 +41,7 @@ class GDFXEntry {
X_FILE_ATTRIBUTES attributes; X_FILE_ATTRIBUTES attributes;
size_t offset; size_t offset;
size_t size; size_t size;
child_t children;
std::vector<GDFXEntry*> children;
}; };
class GDFX { class GDFX {

View File

@ -100,6 +100,20 @@ STFSEntry::STFSEntry()
update_timestamp(0), update_timestamp(0),
access_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) { STFSEntry* STFSEntry::GetChild(const char* name) {
// TODO(benvanik): a faster search // TODO(benvanik): a faster search
for (const auto& entry : children) { for (const auto& entry : children) {

View File

@ -17,6 +17,7 @@
#include "xenia/common.h" #include "xenia/common.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
#include "xenia/kernel/fs/entry.h" #include "xenia/kernel/fs/entry.h"
#include "poly/fs.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
@ -131,6 +132,10 @@ class STFSEntry {
public: public:
STFSEntry(); STFSEntry();
typedef std::vector<std::unique_ptr<STFSEntry>> 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); STFSEntry* GetChild(const char* name);
void Dump(int indent); void Dump(int indent);
@ -141,8 +146,7 @@ class STFSEntry {
size_t size; size_t size;
uint32_t update_timestamp; uint32_t update_timestamp;
uint32_t access_timestamp; uint32_t access_timestamp;
child_t children;
std::vector<std::unique_ptr<STFSEntry>> children;
typedef struct { typedef struct {
size_t offset; size_t offset;