From c9ddd5b0aed813a495cc13882b72da366f881845 Mon Sep 17 00:00:00 2001 From: Subv Date: Thu, 7 Dec 2017 14:38:19 -0500 Subject: [PATCH] HLE/FS: Implemented FSFile::OpenSubFile. The File class now holds a list of connected sessions along with data unique to each session. A subfile is a window into an existing file. They have a few limitations compared to normal files: * They can't be written to. * They can't be flushed. * Their size can not be changed. * New subfiles can't be created from another subfile. --- src/core/hle/kernel/hle_ipc.h | 2 +- src/core/hle/service/am/am.cpp | 6 +- src/core/hle/service/fs/archive.cpp | 165 ++++++++++++++++++++++++++-- src/core/hle/service/fs/archive.h | 27 ++++- src/core/hle/service/fs/fs_user.cpp | 12 +- 5 files changed, 184 insertions(+), 28 deletions(-) diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 2a246fa27..338f3c7d1 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -54,7 +54,7 @@ public: * associated ServerSession. * @param server_session ServerSession associated with the connection. */ - void ClientDisconnected(SharedPtr server_session); + virtual void ClientDisconnected(SharedPtr server_session); protected: /// List of sessions that are connected to this handler. diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9b87bb6da..ebaa2fc6e 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -909,16 +909,12 @@ void BeginImportProgram(Service::Interface* self) { const FileSys::Path cia_path = {}; auto file = std::make_shared(std::make_unique(media_type), cia_path); - auto sessions = Kernel::ServerSession::CreateSessionPair(file->GetName()); - file->ClientConnected(std::get>(sessions)); cia_installing = true; IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); rb.Push(RESULT_SUCCESS); // No error - rb.PushCopyHandles( - Kernel::g_handle_table.Create(std::get>(sessions)) - .Unwrap()); + rb.PushCopyHandles(Kernel::g_handle_table.Create(file->Connect()).Unwrap()); LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", static_cast(media_type)); } diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index be6086c68..92eb4ef04 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -67,8 +68,9 @@ enum class DirectoryCommand : u32 { }; File::File(std::unique_ptr&& backend, const FileSys::Path& path) - : ServiceFramework("", 1), path(path), priority(0), backend(std::move(backend)) { + : ServiceFramework("", 1), path(path), backend(std::move(backend)) { static const FunctionInfo functions[] = { + {0x08010100, &File::OpenSubFile, "OpenSubFile"}, {0x080200C2, &File::Read, "Read"}, {0x08030102, &File::Write, "Write"}, {0x08040000, &File::GetSize, "GetSize"}, @@ -90,6 +92,16 @@ void File::Read(Kernel::HLERequestContext& ctx) { LOG_TRACE(Service_FS, "Read %s: offset=0x%" PRIx64 " length=0x%08X", GetName().c_str(), offset, length); + const SessionSlot& file = GetSessionSlot(ctx.Session()); + + if (file.subfile && length > file.size) { + LOG_WARNING(Service_FS, "Trying to read beyond the subfile size, truncating"); + length = file.size; + } + + // This file session might have a specific offset from where to start reading, apply it. + offset += file.offset; + if (offset + length > backend->GetSize()) { LOG_ERROR(Service_FS, "Reading from out of bounds offset=0x%" PRIx64 " length=0x%08X file_size=0x%" PRIx64, @@ -122,6 +134,16 @@ void File::Write(Kernel::HLERequestContext& ctx) { IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + const SessionSlot& file = GetSessionSlot(ctx.Session()); + + // Subfiles can not be written to + if (file.subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + rb.Push(0); + rb.PushMappedBuffer(buffer); + return; + } + std::vector data(length); buffer.Read(data.data(), 0, data.size()); ResultVal written = backend->Write(offset, data.size(), flush != 0, data.data()); @@ -137,20 +159,41 @@ void File::Write(Kernel::HLERequestContext& ctx) { void File::GetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0804, 0, 0); + + const SessionSlot& file = GetSessionSlot(ctx.Session()); + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); rb.Push(RESULT_SUCCESS); - rb.Push(backend->GetSize()); + rb.Push(file.size); } void File::SetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0805, 2, 0); - backend->SetSize(rp.Pop()); + u64 size = rp.Pop(); + + SessionSlot& file = GetSessionSlot(ctx.Session()); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + // SetSize can not be called on subfiles. + if (file.subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + file.size = size; + backend->SetSize(size); rb.Push(RESULT_SUCCESS); } void File::Close(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0808, 0, 0); + + // TODO(Subv): Only close the backend if this client is the only one left. + if (session_slots.size() > 1) + LOG_WARNING(Service_FS, "Closing File backend but %zu clients still connected", + session_slots.size()); + backend->Close(); IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); @@ -158,23 +201,38 @@ void File::Close(Kernel::HLERequestContext& ctx) { void File::Flush(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x0809, 0, 0); - backend->Flush(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + const SessionSlot& file = GetSessionSlot(ctx.Session()); + + // Subfiles can not be flushed. + if (file.subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + backend->Flush(); rb.Push(RESULT_SUCCESS); } void File::SetPriority(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x080A, 1, 0); - priority = rp.Pop(); + + SessionSlot& file = GetSessionSlot(ctx.Session()); + file.priority = rp.Pop(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); rb.Push(RESULT_SUCCESS); } void File::GetPriority(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x080B, 0, 0); + const SessionSlot& file = GetSessionSlot(ctx.Session()); + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); rb.Push(RESULT_SUCCESS); - rb.Push(priority); + rb.Push(file.priority); } void File::OpenLinkFile(Kernel::HLERequestContext& ctx) { @@ -185,13 +243,104 @@ void File::OpenLinkFile(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx, 0x080C, 0, 0); IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); auto sessions = ServerSession::CreateSessionPair(GetName()); - ClientConnected(std::get>(sessions)); + auto server = std::get>(sessions); + ClientConnected(server); + + const SessionSlot& original_file = GetSessionSlot(ctx.Session()); + + SessionSlot slot{}; + slot.priority = original_file.priority; + slot.offset = 0; + slot.size = backend->GetSize(); + slot.session = server; + slot.subfile = false; + + session_slots.emplace_back(std::move(slot)); rb.Push(RESULT_SUCCESS); rb.PushMoveObjects(std::get>(sessions)); } -File::~File() {} +void File::OpenSubFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0801, 4, 0); + s64 offset = rp.PopRaw(); + s64 size = rp.PopRaw(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + const SessionSlot& original_file = GetSessionSlot(ctx.Session()); + + if (original_file.subfile) { + // OpenSubFile can not be called on a file which is already as subfile + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + if (offset < 0 || size < 0) { + rb.Push(FileSys::ERR_WRITE_BEYOND_END); + return; + } + + size_t end = offset + size; + + // TODO(Subv): Check for overflow and return ERR_WRITE_BEYOND_END + + if (end > original_file.size) { + rb.Push(FileSys::ERR_WRITE_BEYOND_END); + return; + } + + using Kernel::ClientSession; + using Kernel::ServerSession; + using Kernel::SharedPtr; + auto sessions = ServerSession::CreateSessionPair(GetName()); + auto server = std::get>(sessions); + ClientConnected(server); + + SessionSlot slot{}; + slot.priority = original_file.priority; + slot.offset = offset; + slot.size = size; + slot.session = server; + slot.subfile = true; + + session_slots.emplace_back(std::move(slot)); + + rb.Push(RESULT_SUCCESS); + rb.PushMoveObjects(std::get>(sessions)); +} + +File::SessionSlot& File::GetSessionSlot(Kernel::SharedPtr session) { + auto itr = std::find_if(session_slots.begin(), session_slots.end(), + [&](const SessionSlot& slot) { return slot.session == session; }); + ASSERT(itr != session_slots.end()); + return *itr; +} + +void File::ClientDisconnected(Kernel::SharedPtr server_session) { + session_slots.erase( + std::remove_if(session_slots.begin(), session_slots.end(), + [&](const SessionSlot& slot) { return slot.session == server_session; }), + session_slots.end()); + SessionRequestHandler::ClientDisconnected(server_session); +} + +Kernel::SharedPtr File::Connect() { + auto sessions = Kernel::ServerSession::CreateSessionPair(GetName()); + auto server = std::get>(sessions); + ClientConnected(server); + + SessionSlot slot{}; + slot.priority = 0; + slot.offset = 0; + slot.size = backend->GetSize(); + slot.session = server; + slot.subfile = false; + + session_slots.emplace_back(std::move(slot)); + + return std::get>(sessions); +} Directory::Directory(std::unique_ptr&& backend, const FileSys::Path& path) diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index b9a9637e1..01d67c3d0 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -6,16 +6,18 @@ #include #include +#include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" #include "core/hle/kernel/hle_ipc.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/result.h" #include "core/hle/service/service.h" namespace FileSys { class DirectoryBackend; class FileBackend; -} +} // namespace FileSys /// The unique system identifier hash, also known as ID0 static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; @@ -53,16 +55,20 @@ typedef u64 ArchiveHandle; class File final : public ServiceFramework { public: File(std::unique_ptr&& backend, const FileSys::Path& path); - ~File(); + ~File() = default; std::string GetName() const { return "Path: " + path.DebugStr(); } - FileSys::Path path; ///< Path of the file - u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means + FileSys::Path path; ///< Path of the file std::unique_ptr backend; ///< File backend interface + /// Creates a new session to this File and returns the ClientSession part of the connection. + Kernel::SharedPtr Connect(); + + void ClientDisconnected(Kernel::SharedPtr server_session) override; + private: void Read(Kernel::HLERequestContext& ctx); void Write(Kernel::HLERequestContext& ctx); @@ -73,6 +79,19 @@ private: void SetPriority(Kernel::HLERequestContext& ctx); void GetPriority(Kernel::HLERequestContext& ctx); void OpenLinkFile(Kernel::HLERequestContext& ctx); + void OpenSubFile(Kernel::HLERequestContext& ctx); + + struct SessionSlot { + Kernel::SharedPtr session; ///< The session that this slot refers to. + u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means + u64 offset; ///< Offset that this session will start reading from. + u64 size; ///< Max size of the file that this session is allowed to access + bool subfile; ///< Whether this file was opened via OpenSubFile or not. + }; + + std::vector session_slots; + + SessionSlot& GetSessionSlot(Kernel::SharedPtr session); }; class Directory final : public Kernel::SessionRequestHandler { diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index bff4c14f1..4e774394b 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -86,11 +86,7 @@ static void OpenFile(Service::Interface* self) { rb.Push(file_res.Code()); if (file_res.Succeeded()) { std::shared_ptr file = *file_res; - auto sessions = ServerSession::CreateSessionPair(file->GetName()); - file->ClientConnected(std::get>(sessions)); - - rb.PushMoveHandles( - Kernel::g_handle_table.Create(std::get>(sessions)).Unwrap()); + rb.PushMoveHandles(Kernel::g_handle_table.Create(file->Connect()).Unwrap()); } else { rb.PushMoveHandles(0); LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str()); @@ -152,11 +148,7 @@ static void OpenFileDirectly(Service::Interface* self) { cmd_buff[1] = file_res.Code().raw; if (file_res.Succeeded()) { std::shared_ptr file = *file_res; - auto sessions = ServerSession::CreateSessionPair(file->GetName()); - file->ClientConnected(std::get>(sessions)); - - cmd_buff[3] = - Kernel::g_handle_table.Create(std::get>(sessions)).Unwrap(); + cmd_buff[3] = Kernel::g_handle_table.Create(file->Connect()).Unwrap(); } else { cmd_buff[3] = 0; LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u",