Move NtCreateFile/NtOpenFile to VFS, implement (mostly) for real.

Progress on #305.
This commit is contained in:
Ben Vanik 2015-06-29 10:33:36 -07:00
parent 0104a2290f
commit cc08e9019a
25 changed files with 459 additions and 159 deletions

View File

@ -16,6 +16,9 @@
#include "xenia/base/string.h" #include "xenia/base/string.h"
// TOODO(benvanik): remove windows headers.
#undef DeleteFile
namespace xe { namespace xe {
namespace filesystem { namespace filesystem {
@ -28,6 +31,7 @@ bool DeleteFolder(const std::wstring& path);
bool IsFolder(const std::wstring& path); bool IsFolder(const std::wstring& path);
FILE* OpenFile(const std::wstring& path, const char* mode); FILE* OpenFile(const std::wstring& path, const char* mode);
bool DeleteFile(const std::wstring& path);
struct FileInfo { struct FileInfo {
enum class Type { enum class Type {
@ -41,6 +45,7 @@ struct FileInfo {
uint64_t access_timestamp; uint64_t access_timestamp;
uint64_t write_timestamp; uint64_t write_timestamp;
}; };
bool GetInfo(const std::wstring& path, FileInfo* out_info);
std::vector<FileInfo> ListFiles(const std::wstring& path); std::vector<FileInfo> ListFiles(const std::wstring& path);
class WildcardFlags { class WildcardFlags {

View File

@ -54,8 +54,33 @@ FILE* OpenFile(const std::wstring& path, const char* mode) {
return _wfopen(fixed_path.c_str(), xe::to_wstring(mode).c_str()); return _wfopen(fixed_path.c_str(), xe::to_wstring(mode).c_str());
} }
bool DeleteFile(const std::wstring& path) {
return DeleteFileW(path.c_str()) ? true : false;
}
#define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime) #define COMBINE_TIME(t) (((uint64_t)t.dwHighDateTime << 32) | t.dwLowDateTime)
bool GetInfo(const std::wstring& path, FileInfo* out_info) {
std::memset(out_info, 0, sizeof(FileInfo));
WIN32_FILE_ATTRIBUTE_DATA data = {0};
if (!GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, &data)) {
return false;
}
if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out_info->type = FileInfo::Type::kDirectory;
out_info->total_size = 0;
} else {
out_info->type = FileInfo::Type::kFile;
out_info->total_size =
(data.nFileSizeHigh * (size_t(MAXDWORD) + 1)) + data.nFileSizeLow;
}
out_info->name = xe::find_name_from_path(path);
out_info->create_timestamp = COMBINE_TIME(data.ftCreationTime);
out_info->access_timestamp = COMBINE_TIME(data.ftLastAccessTime);
out_info->write_timestamp = COMBINE_TIME(data.ftLastWriteTime);
return true;
}
std::vector<FileInfo> ListFiles(const std::wstring& path) { std::vector<FileInfo> ListFiles(const std::wstring& path) {
std::vector<FileInfo> result; std::vector<FileInfo> result;

View File

@ -12,6 +12,7 @@
#include <string> #include <string>
#include "xenia/base/filesystem.h" #include "xenia/base/filesystem.h"
#include "xenia/base/string.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/xobject.h" #include "xenia/kernel/xobject.h"
#include "xenia/vfs/devices/host_path_device.h" #include "xenia/vfs/devices/host_path_device.h"

View File

@ -16,10 +16,10 @@
namespace xe { namespace xe {
namespace kernel { namespace kernel {
XFile::XFile(KernelState* kernel_state, vfs::Mode mode, vfs::Entry* entry) XFile::XFile(KernelState* kernel_state, uint32_t file_access, vfs::Entry* entry)
: XObject(kernel_state, kTypeFile), : XObject(kernel_state, kTypeFile),
entry_(entry), entry_(entry),
mode_(mode), file_access_(file_access),
position_(0), position_(0),
find_index_(0) { find_index_(0) {
async_event_ = new XEvent(kernel_state); async_event_ = new XEvent(kernel_state);

View File

@ -81,6 +81,7 @@ class XFile : public XObject {
vfs::Device* device() const { return entry_->device(); } vfs::Device* device() const { return entry_->device(); }
vfs::Entry* entry() const { return entry_; } vfs::Entry* entry() const { return entry_; }
uint32_t file_access() const { return file_access_; }
const std::string& path() const { return entry_->path(); } const std::string& path() const { return entry_->path(); }
const std::string& name() const { return entry_->name(); } const std::string& name() const { return entry_->name(); }
@ -102,7 +103,7 @@ class XFile : public XObject {
virtual void* GetWaitHandle(); virtual void* GetWaitHandle();
protected: protected:
XFile(KernelState* kernel_state, vfs::Mode mode, vfs::Entry* entry); XFile(KernelState* kernel_state, uint32_t file_access, vfs::Entry* entry);
virtual X_STATUS ReadSync(void* buffer, size_t buffer_length, virtual X_STATUS ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) = 0; size_t byte_offset, size_t* out_bytes_read) = 0;
virtual X_STATUS WriteSync(const void* buffer, size_t buffer_length, virtual X_STATUS WriteSync(const void* buffer, size_t buffer_length,
@ -112,7 +113,7 @@ class XFile : public XObject {
private: private:
vfs::Entry* entry_; vfs::Entry* entry_;
vfs::Mode mode_; uint32_t file_access_;
XEvent* async_event_; XEvent* async_event_;
// TODO(benvanik): create flags, open state, etc. // TODO(benvanik): create flags, open state, etc.

View File

@ -57,9 +57,9 @@ X_STATUS XUserModule::LoadFromFile(std::string path) {
std::vector<uint8_t> buffer(fs_entry->size()); std::vector<uint8_t> buffer(fs_entry->size());
// Open file for reading. // Open file for reading.
XFile* file_ptr = nullptr; object_ref<XFile> file;
result = fs_entry->Open(kernel_state(), vfs::Mode::READ, false, &file_ptr); result =
object_ref<XFile> file(file_ptr); fs_entry->Open(kernel_state(), vfs::FileAccess::kGenericRead, &file);
if (result) { if (result) {
return result; return result;
} }

View File

@ -152,98 +152,45 @@ class X_FILE_FS_ATTRIBUTE_INFORMATION {
}; };
static_assert_size(X_FILE_FS_ATTRIBUTE_INFORMATION, 16); static_assert_size(X_FILE_FS_ATTRIBUTE_INFORMATION, 16);
struct FileDisposition {
static const uint32_t X_FILE_SUPERSEDE = 0x00000000;
static const uint32_t X_FILE_OPEN = 0x00000001;
static const uint32_t X_FILE_CREATE = 0x00000002;
static const uint32_t X_FILE_OPEN_IF = 0x00000003;
static const uint32_t X_FILE_OVERWRITE = 0x00000004;
static const uint32_t X_FILE_OVERWRITE_IF = 0x00000005;
};
struct FileAccess {
static const uint32_t X_GENERIC_READ = 0x80000000;
static const uint32_t X_GENERIC_WRITE = 0x40000000;
static const uint32_t X_GENERIC_EXECUTE = 0x20000000;
static const uint32_t X_GENERIC_ALL = 0x10000000;
static const uint32_t X_FILE_READ_DATA = 0x00000001;
static const uint32_t X_FILE_WRITE_DATA = 0x00000002;
static const uint32_t X_FILE_APPEND_DATA = 0x00000004;
};
X_STATUS NtCreateFile(PPCContext* ppc_context, KernelState* kernel_state, X_STATUS NtCreateFile(PPCContext* ppc_context, KernelState* kernel_state,
uint32_t handle_ptr, uint32_t desired_access, uint32_t handle_ptr, uint32_t desired_access,
X_OBJECT_ATTRIBUTES* object_attrs, X_OBJECT_ATTRIBUTES* object_attrs,
const char* object_name, uint32_t io_status_block_ptr, const char* object_name, uint32_t io_status_block_ptr,
uint32_t allocation_size_ptr, uint32_t file_attributes, uint32_t allocation_size_ptr, uint32_t file_attributes,
uint32_t share_access, uint32_t creation_disposition) { uint32_t share_access,
FileDisposition creation_disposition) {
uint64_t allocation_size = 0; // is this correct??? uint64_t allocation_size = 0; // is this correct???
if (allocation_size_ptr != 0) { if (allocation_size_ptr != 0) {
allocation_size = SHIM_MEM_64(allocation_size_ptr); allocation_size = SHIM_MEM_64(allocation_size_ptr);
} }
X_STATUS result = X_STATUS_NO_SUCH_FILE; // Compute path, possibly attrs relative.
uint32_t info = X_FILE_DOES_NOT_EXIST; std::string target_path = object_name;
uint32_t handle;
auto fs = kernel_state->file_system();
Entry* entry;
object_ref<XFile> root_file;
if (object_attrs->root_directory != 0xFFFFFFFD && // ObDosDevices if (object_attrs->root_directory != 0xFFFFFFFD && // ObDosDevices
object_attrs->root_directory != 0) { object_attrs->root_directory != 0) {
root_file = kernel_state->object_table()->LookupObject<XFile>( auto root_file = kernel_state->object_table()->LookupObject<XFile>(
object_attrs->root_directory); object_attrs->root_directory);
assert_not_null(root_file); assert_not_null(root_file);
assert_true(root_file->type() == XObject::Type::kTypeFile); assert_true(root_file->type() == XObject::Type::kTypeFile);
// Resolve the file using the device the root directory is part of. // Resolve the file using the device the root directory is part of.
auto device = root_file->device(); auto device = root_file->device();
auto target_path = xe::join_paths(root_file->path(), object_name); target_path = xe::join_paths(
entry = device->ResolvePath(target_path.c_str()); device->mount_path(), xe::join_paths(root_file->path(), object_name));
} else {
// Resolve the file using the virtual file system.
entry = fs->ResolvePath(object_name);
}
bool wants_write = desired_access & FileAccess::X_GENERIC_WRITE ||
desired_access & FileAccess::X_GENERIC_ALL ||
desired_access & FileAccess::X_FILE_WRITE_DATA ||
desired_access & FileAccess::X_FILE_APPEND_DATA;
if (wants_write) {
if (entry && entry->is_read_only()) {
// We don't support any write modes.
XELOGW("Attempted to open the file/dir for create/write");
desired_access = FileAccess::X_GENERIC_READ;
}
} }
// Attempt open (or create).
object_ref<XFile> file; object_ref<XFile> file;
if (!entry) { FileAction file_action;
result = X_STATUS_NO_SUCH_FILE; X_STATUS result = kernel_state->file_system()->OpenFile(
info = X_FILE_DOES_NOT_EXIST; kernel_state, target_path, creation_disposition, desired_access, &file,
} else { &file_action);
// Open the file/directory. uint32_t info = uint32_t(file_action);
vfs::Mode mode;
if (desired_access & FileAccess::X_FILE_APPEND_DATA) {
mode = vfs::Mode::READ_APPEND;
} else if (wants_write) {
mode = vfs::Mode::READ_WRITE;
} else {
mode = vfs::Mode::READ;
}
XFile* file_ptr = nullptr;
result = entry->Open(kernel_state, mode,
false, // TODO(benvanik): pick async mode, if needed.
&file_ptr);
file = object_ref<XFile>(file_ptr);
}
X_HANDLE handle = X_INVALID_HANDLE_VALUE;
if (XSUCCEEDED(result)) { if (XSUCCEEDED(result)) {
// Handle ref is incremented, so return that. // Handle ref is incremented, so return that.
handle = file->handle(); handle = file->handle();
result = X_STATUS_SUCCESS;
info = X_FILE_OPENED;
} }
if (io_status_block_ptr) { if (io_status_block_ptr) {
@ -282,7 +229,7 @@ SHIM_CALL NtCreateFile_shim(PPCContext* ppc_context,
auto result = NtCreateFile( auto result = NtCreateFile(
ppc_context, kernel_state, handle_ptr, desired_access, &object_attrs, ppc_context, kernel_state, handle_ptr, desired_access, &object_attrs,
object_name, io_status_block_ptr, allocation_size_ptr, file_attributes, object_name, io_status_block_ptr, allocation_size_ptr, file_attributes,
share_access, creation_disposition); share_access, FileDisposition(creation_disposition));
free(object_name); free(object_name);
SHIM_SET_RETURN_32(result); SHIM_SET_RETURN_32(result);
@ -305,7 +252,7 @@ SHIM_CALL NtOpenFile_shim(PPCContext* ppc_context, KernelState* kernel_state) {
auto result = NtCreateFile( auto result = NtCreateFile(
ppc_context, kernel_state, handle_ptr, desired_access, &object_attrs, ppc_context, kernel_state, handle_ptr, desired_access, &object_attrs,
object_name, io_status_block_ptr, 0, 0, 0, FileDisposition::X_FILE_OPEN); object_name, io_status_block_ptr, 0, 0, 0, FileDisposition::kOpen);
free(object_name); free(object_name);
SHIM_SET_RETURN_32(result); SHIM_SET_RETURN_32(result);
@ -451,7 +398,7 @@ SHIM_CALL NtWriteFile_shim(PPCContext* ppc_context, KernelState* kernel_state) {
// Grab file. // Grab file.
auto file = kernel_state->object_table()->LookupObject<XFile>(file_handle); auto file = kernel_state->object_table()->LookupObject<XFile>(file_handle);
if (!ev) { if (!file) {
result = X_STATUS_INVALID_HANDLE; result = X_STATUS_INVALID_HANDLE;
} }

View File

@ -26,9 +26,11 @@ DiscImageEntry::DiscImageEntry(Device* device, Entry* parent, std::string path,
DiscImageEntry::~DiscImageEntry() = default; DiscImageEntry::~DiscImageEntry() = default;
X_STATUS DiscImageEntry::Open(KernelState* kernel_state, Mode mode, bool async, X_STATUS DiscImageEntry::Open(KernelState* kernel_state,
XFile** out_file) { uint32_t desired_access,
*out_file = new DiscImageFile(kernel_state, mode, this); object_ref<XFile>* out_file) {
*out_file =
object_ref<XFile>(new DiscImageFile(kernel_state, desired_access, this));
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }

View File

@ -31,8 +31,8 @@ class DiscImageEntry : public Entry {
size_t data_offset() const { return data_offset_; } size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; } size_t data_size() const { return data_size_; }
X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, X_STATUS Open(KernelState* kernel_state, uint32_t desired_access,
XFile** out_file) override; object_ref<XFile>* out_file) override;
bool can_map() const override { return true; } bool can_map() const override { return true; }
std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode, std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode,

View File

@ -16,9 +16,9 @@
namespace xe { namespace xe {
namespace vfs { namespace vfs {
DiscImageFile::DiscImageFile(KernelState* kernel_state, Mode mode, DiscImageFile::DiscImageFile(KernelState* kernel_state, uint32_t file_access,
DiscImageEntry* entry) DiscImageEntry* entry)
: XFile(kernel_state, mode, entry), entry_(entry) {} : XFile(kernel_state, file_access, entry), entry_(entry) {}
DiscImageFile::~DiscImageFile() = default; DiscImageFile::~DiscImageFile() = default;

View File

@ -19,7 +19,7 @@ class DiscImageEntry;
class DiscImageFile : public XFile { class DiscImageFile : public XFile {
public: public:
DiscImageFile(KernelState* kernel_state, Mode desired_access, DiscImageFile(KernelState* kernel_state, uint32_t file_access,
DiscImageEntry* entry); DiscImageEntry* entry);
~DiscImageFile() override; ~DiscImageFile() override;

View File

@ -46,25 +46,10 @@ bool HostPathDevice::Initialize() {
void HostPathDevice::PopulateEntry(HostPathEntry* parent_entry) { void HostPathDevice::PopulateEntry(HostPathEntry* parent_entry) {
auto child_infos = xe::filesystem::ListFiles(parent_entry->local_path()); auto child_infos = xe::filesystem::ListFiles(parent_entry->local_path());
for (auto& child_info : child_infos) { for (auto& child_info : child_infos) {
auto child = new HostPathEntry( auto child = HostPathEntry::Create(
this, parent_entry, xe::to_string(child_info.name), this, parent_entry,
xe::join_paths(parent_entry->local_path(), child_info.name)); xe::join_paths(parent_entry->local_path(), child_info.name),
child->create_timestamp_ = child_info.create_timestamp; child_info);
child->access_timestamp_ = child_info.access_timestamp;
child->write_timestamp_ = child_info.write_timestamp;
if (child_info.type == xe::filesystem::FileInfo::Type::kDirectory) {
child->attributes_ = kFileAttributeDirectory;
} else {
child->attributes_ = kFileAttributeNormal;
if (read_only_) {
child->attributes_ |= kFileAttributeReadOnly;
}
child->size_ = child_info.total_size;
child->allocation_size_ =
xe::round_up(child_info.total_size, bytes_per_sector());
}
parent_entry->children_.push_back(std::unique_ptr<Entry>(child)); parent_entry->children_.push_back(std::unique_ptr<Entry>(child));
if (child_info.type == xe::filesystem::FileInfo::Type::kDirectory) { if (child_info.type == xe::filesystem::FileInfo::Type::kDirectory) {

View File

@ -9,6 +9,7 @@
#include "xenia/vfs/devices/host_path_entry.h" #include "xenia/vfs/devices/host_path_entry.h"
#include "xenia/base/logging.h"
#include "xenia/base/mapped_memory.h" #include "xenia/base/mapped_memory.h"
#include "xenia/base/math.h" #include "xenia/base/math.h"
#include "xenia/base/string.h" #include "xenia/base/string.h"
@ -23,41 +24,70 @@ HostPathEntry::HostPathEntry(Device* device, Entry* parent, std::string path,
HostPathEntry::~HostPathEntry() = default; HostPathEntry::~HostPathEntry() = default;
X_STATUS HostPathEntry::Open(KernelState* kernel_state, Mode mode, bool async, HostPathEntry* HostPathEntry::Create(Device* device, Entry* parent,
XFile** out_file) { const std::wstring& full_path,
// TODO(benvanik): plumb through proper disposition/access mode. xe::filesystem::FileInfo file_info) {
DWORD desired_access = auto entry = new HostPathEntry(device, parent, xe::to_string(file_info.name),
is_read_only() ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); full_path);
if (mode == Mode::READ_APPEND) { entry->create_timestamp_ = file_info.create_timestamp;
desired_access |= FILE_APPEND_DATA; entry->access_timestamp_ = file_info.access_timestamp;
entry->write_timestamp_ = file_info.write_timestamp;
if (file_info.type == xe::filesystem::FileInfo::Type::kDirectory) {
entry->attributes_ = kFileAttributeDirectory;
} else {
entry->attributes_ = kFileAttributeNormal;
if (device->is_read_only()) {
entry->attributes_ |= kFileAttributeReadOnly;
}
entry->size_ = file_info.total_size;
entry->allocation_size_ =
xe::round_up(file_info.total_size, device->bytes_per_sector());
}
return entry;
}
X_STATUS HostPathEntry::Open(KernelState* kernel_state, uint32_t desired_access,
object_ref<XFile>* out_file) {
if (is_read_only() && (desired_access & (FileAccess::kFileWriteData |
FileAccess::kFileAppendData))) {
XELOGE("Attempting to open file for write access on read-only device");
return X_STATUS_ACCESS_DENIED;
}
DWORD open_access = 0;
if (desired_access & FileAccess::kGenericRead) {
open_access |= GENERIC_READ;
}
if (desired_access & FileAccess::kGenericWrite) {
open_access |= GENERIC_WRITE;
}
if (desired_access & FileAccess::kGenericExecute) {
open_access |= GENERIC_EXECUTE;
}
if (desired_access & FileAccess::kGenericAll) {
open_access |= GENERIC_ALL;
}
if (desired_access & FileAccess::kFileReadData) {
open_access |= FILE_READ_DATA;
}
if (desired_access & FileAccess::kFileWriteData) {
open_access |= FILE_WRITE_DATA;
}
if (desired_access & FileAccess::kFileAppendData) {
open_access |= FILE_APPEND_DATA;
} }
DWORD share_mode = FILE_SHARE_READ; DWORD share_mode = FILE_SHARE_READ;
DWORD creation_disposition; // We assume we've already created the file in the caller.
switch (mode) { DWORD creation_disposition = OPEN_EXISTING;
case Mode::READ:
creation_disposition = OPEN_EXISTING;
break;
case Mode::READ_WRITE:
creation_disposition = OPEN_ALWAYS;
break;
case Mode::READ_APPEND:
creation_disposition = OPEN_EXISTING;
break;
default:
assert_unhandled_case(mode);
break;
}
DWORD flags_and_attributes = async ? FILE_FLAG_OVERLAPPED : 0;
HANDLE file = HANDLE file =
CreateFileW(local_path_.c_str(), desired_access, share_mode, NULL, CreateFileW(local_path_.c_str(), desired_access, share_mode, NULL,
creation_disposition, creation_disposition, FILE_FLAG_BACKUP_SEMANTICS, NULL);
flags_and_attributes | FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (file == INVALID_HANDLE_VALUE) { if (file == INVALID_HANDLE_VALUE) {
// TODO(benvanik): pick correct response. // TODO(benvanik): pick correct response.
return X_STATUS_NO_SUCH_FILE; return X_STATUS_NO_SUCH_FILE;
} }
*out_file = new HostPathFile(kernel_state, mode, this, file); *out_file = object_ref<XFile>(
new HostPathFile(kernel_state, desired_access, this, file));
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
@ -67,5 +97,38 @@ std::unique_ptr<MappedMemory> HostPathEntry::OpenMapped(MappedMemory::Mode mode,
return MappedMemory::Open(local_path_, mode, offset, length); return MappedMemory::Open(local_path_, mode, offset, length);
} }
std::unique_ptr<Entry> HostPathEntry::CreateEntryInternal(std::string name,
uint32_t attributes) {
auto full_path = xe::join_paths(local_path_, xe::to_wstring(name));
if (attributes & kFileAttributeDirectory) {
if (!xe::filesystem::CreateFolder(full_path)) {
return nullptr;
}
} else {
auto file = xe::filesystem::OpenFile(full_path, "wb");
if (!file) {
return nullptr;
}
fclose(file);
}
xe::filesystem::FileInfo file_info;
if (!xe::filesystem::GetInfo(full_path, &file_info)) {
return nullptr;
}
return std::unique_ptr<Entry>(
HostPathEntry::Create(device_, this, full_path, file_info));
}
bool HostPathEntry::DeleteEntryInternal(Entry* entry) {
auto full_path = xe::join_paths(local_path_, xe::to_wstring(entry->name()));
if (entry->attributes() & kFileAttributeDirectory) {
// Delete entire directory and contents.
return xe::filesystem::DeleteFolder(full_path);
} else {
// Delete file.
return xe::filesystem::DeleteFile(full_path);
}
}
} // namespace vfs } // namespace vfs
} // namespace xe } // namespace xe

View File

@ -12,6 +12,7 @@
#include <string> #include <string>
#include "xenia/base/filesystem.h"
#include "xenia/vfs/entry.h" #include "xenia/vfs/entry.h"
namespace xe { namespace xe {
@ -25,10 +26,14 @@ class HostPathEntry : public Entry {
const std::wstring& local_path); const std::wstring& local_path);
~HostPathEntry() override; ~HostPathEntry() override;
static HostPathEntry* Create(Device* device, Entry* parent,
const std::wstring& full_path,
xe::filesystem::FileInfo file_info);
const std::wstring& local_path() { return local_path_; } const std::wstring& local_path() { return local_path_; }
X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, X_STATUS Open(KernelState* kernel_state, uint32_t desired_access,
XFile** out_file) override; object_ref<XFile>* out_file) override;
bool can_map() const override { return true; } bool can_map() const override { return true; }
std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode, std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode,
@ -38,6 +43,10 @@ class HostPathEntry : public Entry {
private: private:
friend class HostPathDevice; friend class HostPathDevice;
std::unique_ptr<Entry> CreateEntryInternal(std::string name,
uint32_t attributes) override;
bool DeleteEntryInternal(Entry* entry) override;
std::wstring local_path_; std::wstring local_path_;
}; };

View File

@ -14,9 +14,9 @@
namespace xe { namespace xe {
namespace vfs { namespace vfs {
HostPathFile::HostPathFile(KernelState* kernel_state, Mode mode, HostPathFile::HostPathFile(KernelState* kernel_state, uint32_t file_access,
HostPathEntry* entry, HANDLE file_handle) HostPathEntry* entry, HANDLE file_handle)
: XFile(kernel_state, mode, entry), : XFile(kernel_state, file_access, entry),
entry_(entry), entry_(entry),
file_handle_(file_handle) {} file_handle_(file_handle) {}
@ -24,6 +24,10 @@ HostPathFile::~HostPathFile() { CloseHandle(file_handle_); }
X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length, X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length,
size_t byte_offset, size_t* out_bytes_read) { size_t byte_offset, size_t* out_bytes_read) {
if (!(file_access() & FileAccess::kFileReadData)) {
return X_STATUS_ACCESS_DENIED;
}
OVERLAPPED overlapped; OVERLAPPED overlapped;
overlapped.Pointer = (PVOID)byte_offset; overlapped.Pointer = (PVOID)byte_offset;
overlapped.hEvent = NULL; overlapped.hEvent = NULL;
@ -41,6 +45,11 @@ X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length,
X_STATUS HostPathFile::WriteSync(const void* buffer, size_t buffer_length, X_STATUS HostPathFile::WriteSync(const void* buffer, size_t buffer_length,
size_t byte_offset, size_t byte_offset,
size_t* out_bytes_written) { size_t* out_bytes_written) {
if (!(file_access() & FileAccess::kFileWriteData |
FileAccess::kFileAppendData)) {
return X_STATUS_ACCESS_DENIED;
}
OVERLAPPED overlapped; OVERLAPPED overlapped;
overlapped.Pointer = (PVOID)byte_offset; overlapped.Pointer = (PVOID)byte_offset;
overlapped.hEvent = NULL; overlapped.hEvent = NULL;

View File

@ -21,8 +21,8 @@ class HostPathEntry;
class HostPathFile : public XFile { class HostPathFile : public XFile {
public: public:
HostPathFile(KernelState* kernel_state, Mode mode, HostPathEntry* entry, HostPathFile(KernelState* kernel_state, uint32_t file_access,
HANDLE file_handle); HostPathEntry* entry, HANDLE file_handle);
~HostPathFile() override; ~HostPathFile() override;
protected: protected:

View File

@ -24,9 +24,11 @@ STFSContainerEntry::STFSContainerEntry(Device* device, Entry* parent,
STFSContainerEntry::~STFSContainerEntry() = default; STFSContainerEntry::~STFSContainerEntry() = default;
X_STATUS STFSContainerEntry::Open(KernelState* kernel_state, Mode mode, X_STATUS STFSContainerEntry::Open(KernelState* kernel_state,
bool async, XFile** out_file) { uint32_t desired_access,
*out_file = new STFSContainerFile(kernel_state, mode, this); object_ref<XFile>* out_file) {
*out_file = object_ref<XFile>(
new STFSContainerFile(kernel_state, desired_access, this));
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }

View File

@ -31,8 +31,8 @@ class STFSContainerEntry : public Entry {
size_t data_offset() const { return data_offset_; } size_t data_offset() const { return data_offset_; }
size_t data_size() const { return data_size_; } size_t data_size() const { return data_size_; }
X_STATUS Open(KernelState* kernel_state, Mode desired_access, bool async, X_STATUS Open(KernelState* kernel_state, uint32_t desired_access,
XFile** out_file) override; object_ref<XFile>* out_file) override;
struct BlockRecord { struct BlockRecord {
size_t offset; size_t offset;

View File

@ -16,9 +16,10 @@
namespace xe { namespace xe {
namespace vfs { namespace vfs {
STFSContainerFile::STFSContainerFile(KernelState* kernel_state, Mode mode, STFSContainerFile::STFSContainerFile(KernelState* kernel_state,
uint32_t file_access,
STFSContainerEntry* entry) STFSContainerEntry* entry)
: XFile(kernel_state, mode, entry), entry_(entry) {} : XFile(kernel_state, file_access, entry), entry_(entry) {}
STFSContainerFile::~STFSContainerFile() = default; STFSContainerFile::~STFSContainerFile() = default;

View File

@ -19,7 +19,7 @@ class STFSContainerEntry;
class STFSContainerFile : public XFile { class STFSContainerFile : public XFile {
public: public:
STFSContainerFile(KernelState* kernel_state, Mode mode, STFSContainerFile(KernelState* kernel_state, uint32_t file_access,
STFSContainerEntry* entry); STFSContainerEntry* entry);
~STFSContainerFile() override; ~STFSContainerFile() override;

View File

@ -70,5 +70,54 @@ Entry* Entry::IterateChildren(const xe::filesystem::WildcardEngine& engine,
return nullptr; return nullptr;
} }
Entry* Entry::CreateEntry(std::string name, uint32_t attributes) {
if (is_read_only()) {
return nullptr;
}
std::lock_guard<xe::mutex> lock(device_->mutex());
if (GetChild(name)) {
// Already exists.
return nullptr;
}
auto entry = CreateEntryInternal(name, attributes);
if (!entry) {
return nullptr;
}
children_.push_back(std::move(entry));
// TODO(benvanik): resort? would break iteration?
Touch();
return children_.back().get();
}
bool Entry::Delete(Entry* entry) {
if (is_read_only()) {
return false;
}
std::lock_guard<xe::mutex> lock(device_->mutex());
if (entry->parent() != this) {
return false;
}
if (!DeleteEntryInternal(entry)) {
return false;
}
for (auto& it = children_.begin(); it != children_.end(); ++it) {
if (it->get() == entry) {
children_.erase(it);
break;
}
}
Touch();
return true;
}
bool Entry::Delete() {
assert_not_null(parent_);
return parent_->Delete(this);
}
void Entry::Touch() {
// TODO(benvanik): update timestamps.
}
} // namespace vfs } // namespace vfs
} // namespace xe } // namespace xe

View File

@ -16,6 +16,7 @@
#include "xenia/base/mapped_memory.h" #include "xenia/base/mapped_memory.h"
#include "xenia/base/string_buffer.h" #include "xenia/base/string_buffer.h"
#include "xenia/kernel/xobject.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
namespace xe { namespace xe {
@ -35,10 +36,41 @@ using namespace xe::kernel;
class Device; class Device;
enum class Mode { // Matches http://source.winehq.org/source/include/winternl.h#1591.
READ, enum class FileAction {
READ_WRITE, kSuperseded = 0,
READ_APPEND, kOpened = 1,
kCreated = 2,
kOverwritten = 3,
kExists = 4,
kDoesNotExist = 5,
};
enum class FileDisposition {
// If exist replace, else create.
kSuperscede = 0,
// If exist open, else error.
kOpen = 1,
// If exist error, else create.
kCreate = 2,
// If exist open, else create.
kOpenIf = 3,
// If exist open and overwrite, else error.
kOverwrite = 4,
// If exist open and overwrite, else create.
kOverwriteIf = 5,
};
struct FileAccess {
// Implies kFileReadData.
static const uint32_t kGenericRead = 0x80000000;
// Implies kFileWriteData.
static const uint32_t kGenericWrite = 0x40000000;
static const uint32_t kGenericExecute = 0x20000000;
static const uint32_t kGenericAll = 0x10000000;
static const uint32_t kFileReadData = 0x00000001;
static const uint32_t kFileWriteData = 0x00000002;
static const uint32_t kFileAppendData = 0x00000004;
}; };
enum FileAttributeFlags : uint32_t { enum FileAttributeFlags : uint32_t {
@ -81,8 +113,13 @@ class Entry {
Entry* IterateChildren(const xe::filesystem::WildcardEngine& engine, Entry* IterateChildren(const xe::filesystem::WildcardEngine& engine,
size_t* current_index); size_t* current_index);
virtual X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, Entry* CreateEntry(std::string name, uint32_t attributes);
XFile** out_file) = 0; bool Delete(Entry* entry);
bool Delete();
void Touch();
virtual X_STATUS Open(KernelState* kernel_state, uint32_t desired_access,
object_ref<XFile>* out_file) = 0;
virtual bool can_map() const { return false; } virtual bool can_map() const { return false; }
virtual std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode, virtual std::unique_ptr<MappedMemory> OpenMapped(MappedMemory::Mode mode,
@ -94,6 +131,12 @@ class Entry {
protected: protected:
Entry(Device* device, Entry* parent, const std::string& path); Entry(Device* device, Entry* parent, const std::string& path);
virtual std::unique_ptr<Entry> CreateEntryInternal(std::string name,
uint32_t attributes) {
return nullptr;
}
virtual bool DeleteEntryInternal(Entry* entry) { return false; }
Device* device_; Device* device_;
Entry* parent_; Entry* parent_;
std::string path_; std::string path_;

View File

@ -12,6 +12,7 @@
#include "xenia/base/filesystem.h" #include "xenia/base/filesystem.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/string.h" #include "xenia/base/string.h"
#include "xenia/kernel/objects/xfile.h"
namespace xe { namespace xe {
namespace vfs { namespace vfs {
@ -93,5 +94,161 @@ Entry* VirtualFileSystem::ResolvePath(std::string path) {
return nullptr; return nullptr;
} }
Entry* VirtualFileSystem::ResolveBasePath(std::string path) {
auto base_path = xe::find_base_path(path);
return ResolvePath(base_path);
}
Entry* VirtualFileSystem::CreatePath(std::string path, uint32_t attributes) {
// Create all required directories recursively.
auto path_parts = xe::split_path(path);
if (path_parts.empty()) {
return nullptr;
}
auto partial_path = path_parts[0];
auto partial_entry = ResolvePath(partial_path);
if (!partial_entry) {
return nullptr;
}
auto parent_entry = partial_entry;
for (size_t i = 1; i < path_parts.size() - 1; ++i) {
partial_path = xe::join_paths(partial_path, path_parts[i]);
auto child_entry = ResolvePath(partial_path);
if (!child_entry) {
child_entry =
parent_entry->CreateEntry(path_parts[i], kFileAttributeDirectory);
}
if (!child_entry) {
return nullptr;
}
parent_entry = child_entry;
}
return parent_entry->CreateEntry(path_parts[path_parts.size() - 1],
attributes);
}
bool VirtualFileSystem::DeletePath(std::string path) {
auto entry = ResolvePath(path);
if (!entry) {
return false;
}
auto parent = entry->parent();
if (!parent) {
// Can't delete root.
return false;
}
return parent->Delete(entry);
}
X_STATUS VirtualFileSystem::OpenFile(KernelState* kernel_state,
std::string path,
FileDisposition creation_disposition,
uint32_t desired_access,
object_ref<XFile>* out_file,
FileAction* out_action) {
// Cleanup access.
if (desired_access & FileAccess::kGenericRead) {
desired_access |= FileAccess::kFileReadData;
}
if (desired_access & FileAccess::kGenericWrite) {
desired_access |= FileAccess::kFileWriteData;
}
if (desired_access & FileAccess::kGenericAll) {
desired_access |= FileAccess::kFileReadData | FileAccess::kFileWriteData;
}
// Lookup host device/parent path.
// If no device or parent, fail.
auto parent_entry = ResolveBasePath(path);
if (!parent_entry) {
*out_action = FileAction::kDoesNotExist;
return X_STATUS_NO_SUCH_FILE;
}
// Check if exists (if we need it to), or that it doesn't (if it shouldn't).
auto file_name = xe::find_name_from_path(path);
Entry* entry = parent_entry->GetChild(file_name);
switch (creation_disposition) {
case FileDisposition::kOpen:
case FileDisposition::kOverwrite:
// Must exist.
if (!entry) {
*out_action = FileAction::kDoesNotExist;
return X_STATUS_NO_SUCH_FILE;
}
break;
case FileDisposition::kCreate:
// Must not exist.
if (entry) {
*out_action = FileAction::kExists;
return X_STATUS_OBJECT_NAME_COLLISION;
}
break;
default:
// Either way, ok.
break;
}
// Verify permissions.
bool wants_write = desired_access & FileAccess::kFileWriteData ||
desired_access & FileAccess::kFileAppendData;
if (wants_write && parent_entry->is_read_only()) {
// Fail if read only device and wants write.
// return X_STATUS_ACCESS_DENIED;
// TODO(benvanik): figure out why games are opening read-only files with
// write modes.
assert_always();
XELOGW("Attempted to open the file/dir for create/write");
desired_access = FileAccess::kGenericRead | FileAccess::kFileReadData;
}
bool created = false;
if (!entry) {
// Remember that we are creating this new, instead of replacing.
created = true;
*out_action = FileAction::kCreated;
} else {
// May need to delete, if it exists.
switch (creation_disposition) {
case FileDisposition::kSuperscede:
// Replace (by delete + recreate).
if (!entry->Delete()) {
return X_STATUS_ACCESS_DENIED;
}
entry = nullptr;
*out_action = FileAction::kSuperseded;
break;
case FileDisposition::kOpen:
case FileDisposition::kOpenIf:
// Normal open.
*out_action = FileAction::kOpened;
break;
case FileDisposition::kOverwrite:
case FileDisposition::kOverwriteIf:
// Overwrite (we do by delete + recreate).
if (!entry->Delete()) {
return X_STATUS_ACCESS_DENIED;
}
entry = nullptr;
*out_action = FileAction::kOverwritten;
break;
}
}
if (!entry) {
// Create if needed (either new or as a replacement).
entry = CreatePath(path, kFileAttributeNormal);
if (!entry) {
return X_STATUS_ACCESS_DENIED;
}
}
// Open.
auto result = entry->Open(kernel_state, desired_access, out_file);
if (XFAILED(result)) {
*out_action = FileAction::kDoesNotExist;
}
return result;
}
} // namespace vfs } // namespace vfs
} // namespace xe } // namespace xe

View File

@ -16,6 +16,7 @@
#include <vector> #include <vector>
#include "xenia/base/mutex.h" #include "xenia/base/mutex.h"
#include "xenia/kernel/xobject.h"
#include "xenia/vfs/device.h" #include "xenia/vfs/device.h"
#include "xenia/vfs/entry.h" #include "xenia/vfs/entry.h"
@ -33,6 +34,15 @@ class VirtualFileSystem {
bool UnregisterSymbolicLink(std::string path); bool UnregisterSymbolicLink(std::string path);
Entry* ResolvePath(std::string path); Entry* ResolvePath(std::string path);
Entry* ResolveBasePath(std::string path);
Entry* CreatePath(std::string path, uint32_t attributes);
bool DeletePath(std::string path);
X_STATUS OpenFile(KernelState* kernel_state, std::string path,
FileDisposition creation_disposition,
uint32_t desired_access, object_ref<XFile>* out_file,
FileAction* out_action);
private: private:
xe::mutex mutex_; xe::mutex mutex_;

View File

@ -116,15 +116,6 @@ typedef uint32_t X_HRESULT;
#define X_MEM_HEAP 0x40000000 #define X_MEM_HEAP 0x40000000
#define X_MEM_16MB_PAGES 0x80000000 // from Valve SDK #define X_MEM_16MB_PAGES 0x80000000 // from Valve SDK
// FILE_*, used by NtOpenFile
#define X_FILE_SUPERSEDED 0x00000000
#define X_FILE_OPENED 0x00000001
#define X_FILE_CREATED 0x00000002
#define X_FILE_OVERWRITTEN 0x00000003
#define X_FILE_EXISTS 0x00000004
#define X_FILE_DOES_NOT_EXIST 0x00000005
// PAGE_*, used by NtAllocateVirtualMemory // PAGE_*, used by NtAllocateVirtualMemory
#define X_PAGE_NOACCESS 0x00000001 #define X_PAGE_NOACCESS 0x00000001
#define X_PAGE_READONLY 0x00000002 #define X_PAGE_READONLY 0x00000002