From cc08e9019a849148a62da737c3d27da660d0d174 Mon Sep 17 00:00:00 2001 From: Ben Vanik Date: Mon, 29 Jun 2015 10:33:36 -0700 Subject: [PATCH] Move NtCreateFile/NtOpenFile to VFS, implement (mostly) for real. Progress on #305. --- src/xenia/base/filesystem.h | 5 + src/xenia/base/filesystem_win.cc | 25 +++ src/xenia/kernel/content_manager.cc | 1 + src/xenia/kernel/objects/xfile.cc | 4 +- src/xenia/kernel/objects/xfile.h | 5 +- src/xenia/kernel/objects/xuser_module.cc | 6 +- src/xenia/kernel/xboxkrnl_io.cc | 87 ++-------- src/xenia/vfs/devices/disc_image_entry.cc | 8 +- src/xenia/vfs/devices/disc_image_entry.h | 4 +- src/xenia/vfs/devices/disc_image_file.cc | 4 +- src/xenia/vfs/devices/disc_image_file.h | 2 +- src/xenia/vfs/devices/host_path_device.cc | 23 +-- src/xenia/vfs/devices/host_path_entry.cc | 115 ++++++++++--- src/xenia/vfs/devices/host_path_entry.h | 13 +- src/xenia/vfs/devices/host_path_file.cc | 13 +- src/xenia/vfs/devices/host_path_file.h | 4 +- src/xenia/vfs/devices/stfs_container_entry.cc | 8 +- src/xenia/vfs/devices/stfs_container_entry.h | 4 +- src/xenia/vfs/devices/stfs_container_file.cc | 5 +- src/xenia/vfs/devices/stfs_container_file.h | 2 +- src/xenia/vfs/entry.cc | 49 ++++++ src/xenia/vfs/entry.h | 55 +++++- src/xenia/vfs/virtual_file_system.cc | 157 ++++++++++++++++++ src/xenia/vfs/virtual_file_system.h | 10 ++ src/xenia/xbox.h | 9 - 25 files changed, 459 insertions(+), 159 deletions(-) diff --git a/src/xenia/base/filesystem.h b/src/xenia/base/filesystem.h index 1d6c2d914..0c7246196 100644 --- a/src/xenia/base/filesystem.h +++ b/src/xenia/base/filesystem.h @@ -16,6 +16,9 @@ #include "xenia/base/string.h" +// TOODO(benvanik): remove windows headers. +#undef DeleteFile + namespace xe { namespace filesystem { @@ -28,6 +31,7 @@ bool DeleteFolder(const std::wstring& path); bool IsFolder(const std::wstring& path); FILE* OpenFile(const std::wstring& path, const char* mode); +bool DeleteFile(const std::wstring& path); struct FileInfo { enum class Type { @@ -41,6 +45,7 @@ struct FileInfo { uint64_t access_timestamp; uint64_t write_timestamp; }; +bool GetInfo(const std::wstring& path, FileInfo* out_info); std::vector ListFiles(const std::wstring& path); class WildcardFlags { diff --git a/src/xenia/base/filesystem_win.cc b/src/xenia/base/filesystem_win.cc index 568d9f4eb..36a48d596 100644 --- a/src/xenia/base/filesystem_win.cc +++ b/src/xenia/base/filesystem_win.cc @@ -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()); } +bool DeleteFile(const std::wstring& path) { + return DeleteFileW(path.c_str()) ? true : false; +} + #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 ListFiles(const std::wstring& path) { std::vector result; diff --git a/src/xenia/kernel/content_manager.cc b/src/xenia/kernel/content_manager.cc index 5ad2532bd..08daae9b4 100644 --- a/src/xenia/kernel/content_manager.cc +++ b/src/xenia/kernel/content_manager.cc @@ -12,6 +12,7 @@ #include #include "xenia/base/filesystem.h" +#include "xenia/base/string.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/xobject.h" #include "xenia/vfs/devices/host_path_device.h" diff --git a/src/xenia/kernel/objects/xfile.cc b/src/xenia/kernel/objects/xfile.cc index 565632918..0094b18b3 100644 --- a/src/xenia/kernel/objects/xfile.cc +++ b/src/xenia/kernel/objects/xfile.cc @@ -16,10 +16,10 @@ namespace xe { 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), entry_(entry), - mode_(mode), + file_access_(file_access), position_(0), find_index_(0) { async_event_ = new XEvent(kernel_state); diff --git a/src/xenia/kernel/objects/xfile.h b/src/xenia/kernel/objects/xfile.h index f449294c1..dd046c92c 100644 --- a/src/xenia/kernel/objects/xfile.h +++ b/src/xenia/kernel/objects/xfile.h @@ -81,6 +81,7 @@ class XFile : public XObject { vfs::Device* device() const { return entry_->device(); } 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& name() const { return entry_->name(); } @@ -102,7 +103,7 @@ class XFile : public XObject { virtual void* GetWaitHandle(); 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, size_t byte_offset, size_t* out_bytes_read) = 0; virtual X_STATUS WriteSync(const void* buffer, size_t buffer_length, @@ -112,7 +113,7 @@ class XFile : public XObject { private: vfs::Entry* entry_; - vfs::Mode mode_; + uint32_t file_access_; XEvent* async_event_; // TODO(benvanik): create flags, open state, etc. diff --git a/src/xenia/kernel/objects/xuser_module.cc b/src/xenia/kernel/objects/xuser_module.cc index 8bfe1d36a..504a2ab1a 100644 --- a/src/xenia/kernel/objects/xuser_module.cc +++ b/src/xenia/kernel/objects/xuser_module.cc @@ -57,9 +57,9 @@ X_STATUS XUserModule::LoadFromFile(std::string path) { std::vector buffer(fs_entry->size()); // Open file for reading. - XFile* file_ptr = nullptr; - result = fs_entry->Open(kernel_state(), vfs::Mode::READ, false, &file_ptr); - object_ref file(file_ptr); + object_ref file; + result = + fs_entry->Open(kernel_state(), vfs::FileAccess::kGenericRead, &file); if (result) { return result; } diff --git a/src/xenia/kernel/xboxkrnl_io.cc b/src/xenia/kernel/xboxkrnl_io.cc index 84aa246b0..9e8795b69 100644 --- a/src/xenia/kernel/xboxkrnl_io.cc +++ b/src/xenia/kernel/xboxkrnl_io.cc @@ -152,98 +152,45 @@ class X_FILE_FS_ATTRIBUTE_INFORMATION { }; 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, uint32_t handle_ptr, uint32_t desired_access, X_OBJECT_ATTRIBUTES* object_attrs, const char* object_name, uint32_t io_status_block_ptr, 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??? if (allocation_size_ptr != 0) { allocation_size = SHIM_MEM_64(allocation_size_ptr); } - X_STATUS result = X_STATUS_NO_SUCH_FILE; - uint32_t info = X_FILE_DOES_NOT_EXIST; - uint32_t handle; - - auto fs = kernel_state->file_system(); - Entry* entry; - - object_ref root_file; + // Compute path, possibly attrs relative. + std::string target_path = object_name; if (object_attrs->root_directory != 0xFFFFFFFD && // ObDosDevices object_attrs->root_directory != 0) { - root_file = kernel_state->object_table()->LookupObject( + auto root_file = kernel_state->object_table()->LookupObject( object_attrs->root_directory); assert_not_null(root_file); assert_true(root_file->type() == XObject::Type::kTypeFile); // Resolve the file using the device the root directory is part of. auto device = root_file->device(); - auto target_path = xe::join_paths(root_file->path(), object_name); - entry = device->ResolvePath(target_path.c_str()); - } 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; - } + target_path = xe::join_paths( + device->mount_path(), xe::join_paths(root_file->path(), object_name)); } + // Attempt open (or create). object_ref file; - if (!entry) { - result = X_STATUS_NO_SUCH_FILE; - info = X_FILE_DOES_NOT_EXIST; - } else { - // Open the file/directory. - 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(file_ptr); - } + FileAction file_action; + X_STATUS result = kernel_state->file_system()->OpenFile( + kernel_state, target_path, creation_disposition, desired_access, &file, + &file_action); + uint32_t info = uint32_t(file_action); + X_HANDLE handle = X_INVALID_HANDLE_VALUE; if (XSUCCEEDED(result)) { // Handle ref is incremented, so return that. handle = file->handle(); - result = X_STATUS_SUCCESS; - info = X_FILE_OPENED; } if (io_status_block_ptr) { @@ -282,7 +229,7 @@ SHIM_CALL NtCreateFile_shim(PPCContext* ppc_context, auto result = NtCreateFile( ppc_context, kernel_state, handle_ptr, desired_access, &object_attrs, object_name, io_status_block_ptr, allocation_size_ptr, file_attributes, - share_access, creation_disposition); + share_access, FileDisposition(creation_disposition)); free(object_name); SHIM_SET_RETURN_32(result); @@ -305,7 +252,7 @@ SHIM_CALL NtOpenFile_shim(PPCContext* ppc_context, KernelState* kernel_state) { auto result = NtCreateFile( 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); SHIM_SET_RETURN_32(result); @@ -451,7 +398,7 @@ SHIM_CALL NtWriteFile_shim(PPCContext* ppc_context, KernelState* kernel_state) { // Grab file. auto file = kernel_state->object_table()->LookupObject(file_handle); - if (!ev) { + if (!file) { result = X_STATUS_INVALID_HANDLE; } diff --git a/src/xenia/vfs/devices/disc_image_entry.cc b/src/xenia/vfs/devices/disc_image_entry.cc index d916660d3..903477605 100644 --- a/src/xenia/vfs/devices/disc_image_entry.cc +++ b/src/xenia/vfs/devices/disc_image_entry.cc @@ -26,9 +26,11 @@ DiscImageEntry::DiscImageEntry(Device* device, Entry* parent, std::string path, DiscImageEntry::~DiscImageEntry() = default; -X_STATUS DiscImageEntry::Open(KernelState* kernel_state, Mode mode, bool async, - XFile** out_file) { - *out_file = new DiscImageFile(kernel_state, mode, this); +X_STATUS DiscImageEntry::Open(KernelState* kernel_state, + uint32_t desired_access, + object_ref* out_file) { + *out_file = + object_ref(new DiscImageFile(kernel_state, desired_access, this)); return X_STATUS_SUCCESS; } diff --git a/src/xenia/vfs/devices/disc_image_entry.h b/src/xenia/vfs/devices/disc_image_entry.h index 4ef4c6827..34dd515aa 100644 --- a/src/xenia/vfs/devices/disc_image_entry.h +++ b/src/xenia/vfs/devices/disc_image_entry.h @@ -31,8 +31,8 @@ class DiscImageEntry : public Entry { size_t data_offset() const { return data_offset_; } size_t data_size() const { return data_size_; } - X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, - XFile** out_file) override; + X_STATUS Open(KernelState* kernel_state, uint32_t desired_access, + object_ref* out_file) override; bool can_map() const override { return true; } std::unique_ptr OpenMapped(MappedMemory::Mode mode, diff --git a/src/xenia/vfs/devices/disc_image_file.cc b/src/xenia/vfs/devices/disc_image_file.cc index 8a2e706f9..ae9289c28 100644 --- a/src/xenia/vfs/devices/disc_image_file.cc +++ b/src/xenia/vfs/devices/disc_image_file.cc @@ -16,9 +16,9 @@ namespace xe { namespace vfs { -DiscImageFile::DiscImageFile(KernelState* kernel_state, Mode mode, +DiscImageFile::DiscImageFile(KernelState* kernel_state, uint32_t file_access, DiscImageEntry* entry) - : XFile(kernel_state, mode, entry), entry_(entry) {} + : XFile(kernel_state, file_access, entry), entry_(entry) {} DiscImageFile::~DiscImageFile() = default; diff --git a/src/xenia/vfs/devices/disc_image_file.h b/src/xenia/vfs/devices/disc_image_file.h index d01125b67..b2757497b 100644 --- a/src/xenia/vfs/devices/disc_image_file.h +++ b/src/xenia/vfs/devices/disc_image_file.h @@ -19,7 +19,7 @@ class DiscImageEntry; class DiscImageFile : public XFile { public: - DiscImageFile(KernelState* kernel_state, Mode desired_access, + DiscImageFile(KernelState* kernel_state, uint32_t file_access, DiscImageEntry* entry); ~DiscImageFile() override; diff --git a/src/xenia/vfs/devices/host_path_device.cc b/src/xenia/vfs/devices/host_path_device.cc index 1df07d462..7e8449d4d 100644 --- a/src/xenia/vfs/devices/host_path_device.cc +++ b/src/xenia/vfs/devices/host_path_device.cc @@ -46,25 +46,10 @@ bool HostPathDevice::Initialize() { void HostPathDevice::PopulateEntry(HostPathEntry* parent_entry) { auto child_infos = xe::filesystem::ListFiles(parent_entry->local_path()); for (auto& child_info : child_infos) { - auto child = new HostPathEntry( - this, parent_entry, xe::to_string(child_info.name), - xe::join_paths(parent_entry->local_path(), child_info.name)); - child->create_timestamp_ = child_info.create_timestamp; - 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()); - } - + auto child = HostPathEntry::Create( + this, parent_entry, + xe::join_paths(parent_entry->local_path(), child_info.name), + child_info); parent_entry->children_.push_back(std::unique_ptr(child)); if (child_info.type == xe::filesystem::FileInfo::Type::kDirectory) { diff --git a/src/xenia/vfs/devices/host_path_entry.cc b/src/xenia/vfs/devices/host_path_entry.cc index 89ff7d5f4..ec013c40c 100644 --- a/src/xenia/vfs/devices/host_path_entry.cc +++ b/src/xenia/vfs/devices/host_path_entry.cc @@ -9,6 +9,7 @@ #include "xenia/vfs/devices/host_path_entry.h" +#include "xenia/base/logging.h" #include "xenia/base/mapped_memory.h" #include "xenia/base/math.h" #include "xenia/base/string.h" @@ -23,41 +24,70 @@ HostPathEntry::HostPathEntry(Device* device, Entry* parent, std::string path, HostPathEntry::~HostPathEntry() = default; -X_STATUS HostPathEntry::Open(KernelState* kernel_state, Mode mode, bool async, - XFile** out_file) { - // TODO(benvanik): plumb through proper disposition/access mode. - DWORD desired_access = - is_read_only() ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); - if (mode == Mode::READ_APPEND) { - desired_access |= FILE_APPEND_DATA; +HostPathEntry* HostPathEntry::Create(Device* device, Entry* parent, + const std::wstring& full_path, + xe::filesystem::FileInfo file_info) { + auto entry = new HostPathEntry(device, parent, xe::to_string(file_info.name), + full_path); + entry->create_timestamp_ = file_info.create_timestamp; + 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* 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 creation_disposition; - switch (mode) { - 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; + // We assume we've already created the file in the caller. + DWORD creation_disposition = OPEN_EXISTING; HANDLE file = CreateFileW(local_path_.c_str(), desired_access, share_mode, NULL, - creation_disposition, - flags_and_attributes | FILE_FLAG_BACKUP_SEMANTICS, NULL); + creation_disposition, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (file == INVALID_HANDLE_VALUE) { // TODO(benvanik): pick correct response. return X_STATUS_NO_SUCH_FILE; } - *out_file = new HostPathFile(kernel_state, mode, this, file); + *out_file = object_ref( + new HostPathFile(kernel_state, desired_access, this, file)); return X_STATUS_SUCCESS; } @@ -67,5 +97,38 @@ std::unique_ptr HostPathEntry::OpenMapped(MappedMemory::Mode mode, return MappedMemory::Open(local_path_, mode, offset, length); } +std::unique_ptr 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( + 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 xe diff --git a/src/xenia/vfs/devices/host_path_entry.h b/src/xenia/vfs/devices/host_path_entry.h index 917c0b43d..9078c09c8 100644 --- a/src/xenia/vfs/devices/host_path_entry.h +++ b/src/xenia/vfs/devices/host_path_entry.h @@ -12,6 +12,7 @@ #include +#include "xenia/base/filesystem.h" #include "xenia/vfs/entry.h" namespace xe { @@ -25,10 +26,14 @@ class HostPathEntry : public Entry { const std::wstring& local_path); ~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_; } - X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, - XFile** out_file) override; + X_STATUS Open(KernelState* kernel_state, uint32_t desired_access, + object_ref* out_file) override; bool can_map() const override { return true; } std::unique_ptr OpenMapped(MappedMemory::Mode mode, @@ -38,6 +43,10 @@ class HostPathEntry : public Entry { private: friend class HostPathDevice; + std::unique_ptr CreateEntryInternal(std::string name, + uint32_t attributes) override; + bool DeleteEntryInternal(Entry* entry) override; + std::wstring local_path_; }; diff --git a/src/xenia/vfs/devices/host_path_file.cc b/src/xenia/vfs/devices/host_path_file.cc index 87fafd634..07fa83f7e 100644 --- a/src/xenia/vfs/devices/host_path_file.cc +++ b/src/xenia/vfs/devices/host_path_file.cc @@ -14,9 +14,9 @@ namespace xe { namespace vfs { -HostPathFile::HostPathFile(KernelState* kernel_state, Mode mode, +HostPathFile::HostPathFile(KernelState* kernel_state, uint32_t file_access, HostPathEntry* entry, HANDLE file_handle) - : XFile(kernel_state, mode, entry), + : XFile(kernel_state, file_access, entry), entry_(entry), file_handle_(file_handle) {} @@ -24,6 +24,10 @@ HostPathFile::~HostPathFile() { CloseHandle(file_handle_); } X_STATUS HostPathFile::ReadSync(void* buffer, size_t buffer_length, size_t byte_offset, size_t* out_bytes_read) { + if (!(file_access() & FileAccess::kFileReadData)) { + return X_STATUS_ACCESS_DENIED; + } + OVERLAPPED overlapped; overlapped.Pointer = (PVOID)byte_offset; 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, size_t byte_offset, size_t* out_bytes_written) { + if (!(file_access() & FileAccess::kFileWriteData | + FileAccess::kFileAppendData)) { + return X_STATUS_ACCESS_DENIED; + } + OVERLAPPED overlapped; overlapped.Pointer = (PVOID)byte_offset; overlapped.hEvent = NULL; diff --git a/src/xenia/vfs/devices/host_path_file.h b/src/xenia/vfs/devices/host_path_file.h index 7e683d8d9..e26eddb21 100644 --- a/src/xenia/vfs/devices/host_path_file.h +++ b/src/xenia/vfs/devices/host_path_file.h @@ -21,8 +21,8 @@ class HostPathEntry; class HostPathFile : public XFile { public: - HostPathFile(KernelState* kernel_state, Mode mode, HostPathEntry* entry, - HANDLE file_handle); + HostPathFile(KernelState* kernel_state, uint32_t file_access, + HostPathEntry* entry, HANDLE file_handle); ~HostPathFile() override; protected: diff --git a/src/xenia/vfs/devices/stfs_container_entry.cc b/src/xenia/vfs/devices/stfs_container_entry.cc index a887f2ea4..c816dfe6e 100644 --- a/src/xenia/vfs/devices/stfs_container_entry.cc +++ b/src/xenia/vfs/devices/stfs_container_entry.cc @@ -24,9 +24,11 @@ STFSContainerEntry::STFSContainerEntry(Device* device, Entry* parent, STFSContainerEntry::~STFSContainerEntry() = default; -X_STATUS STFSContainerEntry::Open(KernelState* kernel_state, Mode mode, - bool async, XFile** out_file) { - *out_file = new STFSContainerFile(kernel_state, mode, this); +X_STATUS STFSContainerEntry::Open(KernelState* kernel_state, + uint32_t desired_access, + object_ref* out_file) { + *out_file = object_ref( + new STFSContainerFile(kernel_state, desired_access, this)); return X_STATUS_SUCCESS; } diff --git a/src/xenia/vfs/devices/stfs_container_entry.h b/src/xenia/vfs/devices/stfs_container_entry.h index efa7f30c5..d07c09cef 100644 --- a/src/xenia/vfs/devices/stfs_container_entry.h +++ b/src/xenia/vfs/devices/stfs_container_entry.h @@ -31,8 +31,8 @@ class STFSContainerEntry : public Entry { size_t data_offset() const { return data_offset_; } size_t data_size() const { return data_size_; } - X_STATUS Open(KernelState* kernel_state, Mode desired_access, bool async, - XFile** out_file) override; + X_STATUS Open(KernelState* kernel_state, uint32_t desired_access, + object_ref* out_file) override; struct BlockRecord { size_t offset; diff --git a/src/xenia/vfs/devices/stfs_container_file.cc b/src/xenia/vfs/devices/stfs_container_file.cc index cdbabc887..9f5288bcb 100644 --- a/src/xenia/vfs/devices/stfs_container_file.cc +++ b/src/xenia/vfs/devices/stfs_container_file.cc @@ -16,9 +16,10 @@ namespace xe { namespace vfs { -STFSContainerFile::STFSContainerFile(KernelState* kernel_state, Mode mode, +STFSContainerFile::STFSContainerFile(KernelState* kernel_state, + uint32_t file_access, STFSContainerEntry* entry) - : XFile(kernel_state, mode, entry), entry_(entry) {} + : XFile(kernel_state, file_access, entry), entry_(entry) {} STFSContainerFile::~STFSContainerFile() = default; diff --git a/src/xenia/vfs/devices/stfs_container_file.h b/src/xenia/vfs/devices/stfs_container_file.h index a39f1107c..869848414 100644 --- a/src/xenia/vfs/devices/stfs_container_file.h +++ b/src/xenia/vfs/devices/stfs_container_file.h @@ -19,7 +19,7 @@ class STFSContainerEntry; class STFSContainerFile : public XFile { public: - STFSContainerFile(KernelState* kernel_state, Mode mode, + STFSContainerFile(KernelState* kernel_state, uint32_t file_access, STFSContainerEntry* entry); ~STFSContainerFile() override; diff --git a/src/xenia/vfs/entry.cc b/src/xenia/vfs/entry.cc index 2648f3cb8..2aa2122dd 100644 --- a/src/xenia/vfs/entry.cc +++ b/src/xenia/vfs/entry.cc @@ -70,5 +70,54 @@ Entry* Entry::IterateChildren(const xe::filesystem::WildcardEngine& engine, return nullptr; } +Entry* Entry::CreateEntry(std::string name, uint32_t attributes) { + if (is_read_only()) { + return nullptr; + } + std::lock_guard 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 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 xe diff --git a/src/xenia/vfs/entry.h b/src/xenia/vfs/entry.h index fd8166c69..e7799f127 100644 --- a/src/xenia/vfs/entry.h +++ b/src/xenia/vfs/entry.h @@ -16,6 +16,7 @@ #include "xenia/base/mapped_memory.h" #include "xenia/base/string_buffer.h" +#include "xenia/kernel/xobject.h" #include "xenia/xbox.h" namespace xe { @@ -35,10 +36,41 @@ using namespace xe::kernel; class Device; -enum class Mode { - READ, - READ_WRITE, - READ_APPEND, +// Matches http://source.winehq.org/source/include/winternl.h#1591. +enum class FileAction { + kSuperseded = 0, + 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 { @@ -81,8 +113,13 @@ class Entry { Entry* IterateChildren(const xe::filesystem::WildcardEngine& engine, size_t* current_index); - virtual X_STATUS Open(KernelState* kernel_state, Mode mode, bool async, - XFile** out_file) = 0; + Entry* CreateEntry(std::string name, uint32_t attributes); + bool Delete(Entry* entry); + bool Delete(); + void Touch(); + + virtual X_STATUS Open(KernelState* kernel_state, uint32_t desired_access, + object_ref* out_file) = 0; virtual bool can_map() const { return false; } virtual std::unique_ptr OpenMapped(MappedMemory::Mode mode, @@ -94,6 +131,12 @@ class Entry { protected: Entry(Device* device, Entry* parent, const std::string& path); + virtual std::unique_ptr CreateEntryInternal(std::string name, + uint32_t attributes) { + return nullptr; + } + virtual bool DeleteEntryInternal(Entry* entry) { return false; } + Device* device_; Entry* parent_; std::string path_; diff --git a/src/xenia/vfs/virtual_file_system.cc b/src/xenia/vfs/virtual_file_system.cc index b0101fdf9..07705d07e 100644 --- a/src/xenia/vfs/virtual_file_system.cc +++ b/src/xenia/vfs/virtual_file_system.cc @@ -12,6 +12,7 @@ #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/string.h" +#include "xenia/kernel/objects/xfile.h" namespace xe { namespace vfs { @@ -93,5 +94,161 @@ Entry* VirtualFileSystem::ResolvePath(std::string path) { 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* 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 xe diff --git a/src/xenia/vfs/virtual_file_system.h b/src/xenia/vfs/virtual_file_system.h index 760606013..f5f35e41e 100644 --- a/src/xenia/vfs/virtual_file_system.h +++ b/src/xenia/vfs/virtual_file_system.h @@ -16,6 +16,7 @@ #include #include "xenia/base/mutex.h" +#include "xenia/kernel/xobject.h" #include "xenia/vfs/device.h" #include "xenia/vfs/entry.h" @@ -33,6 +34,15 @@ class VirtualFileSystem { bool UnregisterSymbolicLink(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* out_file, + FileAction* out_action); private: xe::mutex mutex_; diff --git a/src/xenia/xbox.h b/src/xenia/xbox.h index 66ed575f7..671bc4c9f 100644 --- a/src/xenia/xbox.h +++ b/src/xenia/xbox.h @@ -116,15 +116,6 @@ typedef uint32_t X_HRESULT; #define X_MEM_HEAP 0x40000000 #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 #define X_PAGE_NOACCESS 0x00000001 #define X_PAGE_READONLY 0x00000002