7e2903cb74
This handle manager more closely mirrors the behaviour of the CTR-OS one. In addition object ref-counts and support for DuplicateHandle have been added. Note that support for DuplicateHandle is still experimental, since parts of the kernel still use Handles internally, which will likely cause troubles if two different handles to the same object are used to e.g. wait on a synchronization primitive.
442 lines
17 KiB
C++
442 lines
17 KiB
C++
// Copyright 2014 Citra Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <memory>
|
|
#include <unordered_map>
|
|
|
|
#include "common/common_types.h"
|
|
#include "common/file_util.h"
|
|
#include "common/make_unique.h"
|
|
#include "common/math_util.h"
|
|
|
|
#include "core/file_sys/archive_savedata.h"
|
|
#include "core/file_sys/archive_backend.h"
|
|
#include "core/file_sys/archive_sdmc.h"
|
|
#include "core/file_sys/directory_backend.h"
|
|
#include "core/hle/service/fs/archive.h"
|
|
#include "core/hle/kernel/session.h"
|
|
#include "core/hle/result.h"
|
|
|
|
// Specializes std::hash for ArchiveIdCode, so that we can use it in std::unordered_map.
|
|
// Workaroung for libstdc++ bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970
|
|
namespace std {
|
|
template <>
|
|
struct hash<Service::FS::ArchiveIdCode> {
|
|
typedef Service::FS::ArchiveIdCode argument_type;
|
|
typedef std::size_t result_type;
|
|
|
|
result_type operator()(const argument_type& id_code) const {
|
|
typedef std::underlying_type<argument_type>::type Type;
|
|
return std::hash<Type>()(static_cast<Type>(id_code));
|
|
}
|
|
};
|
|
}
|
|
|
|
namespace Service {
|
|
namespace FS {
|
|
|
|
// Command to access archive file
|
|
enum class FileCommand : u32 {
|
|
Dummy1 = 0x000100C6,
|
|
Control = 0x040100C4,
|
|
OpenSubFile = 0x08010100,
|
|
Read = 0x080200C2,
|
|
Write = 0x08030102,
|
|
GetSize = 0x08040000,
|
|
SetSize = 0x08050080,
|
|
GetAttributes = 0x08060000,
|
|
SetAttributes = 0x08070040,
|
|
Close = 0x08080000,
|
|
Flush = 0x08090000,
|
|
};
|
|
|
|
// Command to access directory
|
|
enum class DirectoryCommand : u32 {
|
|
Dummy1 = 0x000100C6,
|
|
Control = 0x040100C4,
|
|
Read = 0x08010042,
|
|
Close = 0x08020000,
|
|
};
|
|
|
|
class Archive {
|
|
public:
|
|
Archive(std::unique_ptr<FileSys::ArchiveBackend>&& backend, ArchiveIdCode id_code)
|
|
: backend(std::move(backend)), id_code(id_code) {
|
|
}
|
|
|
|
std::string GetName() const { return "Archive: " + backend->GetName(); }
|
|
|
|
ArchiveIdCode id_code; ///< Id code of the archive
|
|
std::unique_ptr<FileSys::ArchiveBackend> backend; ///< Archive backend interface
|
|
};
|
|
|
|
class File : public Kernel::Session {
|
|
public:
|
|
File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path)
|
|
: backend(std::move(backend)), path(path) {
|
|
}
|
|
|
|
std::string GetName() const override { return "Path: " + path.DebugStr(); }
|
|
|
|
FileSys::Path path; ///< Path of the file
|
|
std::unique_ptr<FileSys::FileBackend> backend; ///< File backend interface
|
|
|
|
ResultVal<bool> SyncRequest() override {
|
|
u32* cmd_buff = Kernel::GetCommandBuffer();
|
|
FileCommand cmd = static_cast<FileCommand>(cmd_buff[0]);
|
|
switch (cmd) {
|
|
|
|
// Read from file...
|
|
case FileCommand::Read:
|
|
{
|
|
u64 offset = cmd_buff[1] | ((u64) cmd_buff[2]) << 32;
|
|
u32 length = cmd_buff[3];
|
|
u32 address = cmd_buff[5];
|
|
LOG_TRACE(Service_FS, "Read %s %s: offset=0x%llx length=%d address=0x%x",
|
|
GetTypeName().c_str(), GetName().c_str(), offset, length, address);
|
|
cmd_buff[2] = backend->Read(offset, length, Memory::GetPointer(address));
|
|
break;
|
|
}
|
|
|
|
// Write to file...
|
|
case FileCommand::Write:
|
|
{
|
|
u64 offset = cmd_buff[1] | ((u64) cmd_buff[2]) << 32;
|
|
u32 length = cmd_buff[3];
|
|
u32 flush = cmd_buff[4];
|
|
u32 address = cmd_buff[6];
|
|
LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x",
|
|
GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush);
|
|
cmd_buff[2] = backend->Write(offset, length, flush, Memory::GetPointer(address));
|
|
break;
|
|
}
|
|
|
|
case FileCommand::GetSize:
|
|
{
|
|
LOG_TRACE(Service_FS, "GetSize %s %s", GetTypeName().c_str(), GetName().c_str());
|
|
u64 size = backend->GetSize();
|
|
cmd_buff[2] = (u32)size;
|
|
cmd_buff[3] = size >> 32;
|
|
break;
|
|
}
|
|
|
|
case FileCommand::SetSize:
|
|
{
|
|
u64 size = cmd_buff[1] | ((u64)cmd_buff[2] << 32);
|
|
LOG_TRACE(Service_FS, "SetSize %s %s size=%llu",
|
|
GetTypeName().c_str(), GetName().c_str(), size);
|
|
backend->SetSize(size);
|
|
break;
|
|
}
|
|
|
|
case FileCommand::Close:
|
|
{
|
|
LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
|
|
backend->Close();
|
|
break;
|
|
}
|
|
|
|
case FileCommand::Flush:
|
|
{
|
|
LOG_TRACE(Service_FS, "Flush");
|
|
backend->Flush();
|
|
break;
|
|
}
|
|
|
|
// Unknown command...
|
|
default:
|
|
LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd);
|
|
ResultCode error = UnimplementedFunction(ErrorModule::FS);
|
|
cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that.
|
|
return error;
|
|
}
|
|
cmd_buff[1] = 0; // No error
|
|
return MakeResult<bool>(false);
|
|
}
|
|
};
|
|
|
|
class Directory : public Kernel::Session {
|
|
public:
|
|
Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, const FileSys::Path& path)
|
|
: backend(std::move(backend)), path(path) {
|
|
}
|
|
|
|
std::string GetName() const override { return "Directory: " + path.DebugStr(); }
|
|
|
|
FileSys::Path path; ///< Path of the directory
|
|
std::unique_ptr<FileSys::DirectoryBackend> backend; ///< File backend interface
|
|
|
|
ResultVal<bool> SyncRequest() override {
|
|
u32* cmd_buff = Kernel::GetCommandBuffer();
|
|
DirectoryCommand cmd = static_cast<DirectoryCommand>(cmd_buff[0]);
|
|
switch (cmd) {
|
|
|
|
// Read from directory...
|
|
case DirectoryCommand::Read:
|
|
{
|
|
u32 count = cmd_buff[1];
|
|
u32 address = cmd_buff[3];
|
|
auto entries = reinterpret_cast<FileSys::Entry*>(Memory::GetPointer(address));
|
|
LOG_TRACE(Service_FS, "Read %s %s: count=%d",
|
|
GetTypeName().c_str(), GetName().c_str(), count);
|
|
|
|
// Number of entries actually read
|
|
cmd_buff[2] = backend->Read(count, entries);
|
|
break;
|
|
}
|
|
|
|
case DirectoryCommand::Close:
|
|
{
|
|
LOG_TRACE(Service_FS, "Close %s %s", GetTypeName().c_str(), GetName().c_str());
|
|
backend->Close();
|
|
break;
|
|
}
|
|
|
|
// Unknown command...
|
|
default:
|
|
LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd);
|
|
ResultCode error = UnimplementedFunction(ErrorModule::FS);
|
|
cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that.
|
|
return MakeResult<bool>(false);
|
|
}
|
|
cmd_buff[1] = 0; // No error
|
|
return MakeResult<bool>(false);
|
|
}
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Map of registered archives, identified by id code. Once an archive is registered here, it is
|
|
* never removed until the FS service is shut down.
|
|
*/
|
|
static std::unordered_map<ArchiveIdCode, std::unique_ptr<Archive>> id_code_map;
|
|
|
|
/**
|
|
* Map of active archive handles. Values are pointers to the archives in `idcode_map`.
|
|
*/
|
|
static std::unordered_map<ArchiveHandle, Archive*> handle_map;
|
|
static ArchiveHandle next_handle;
|
|
|
|
static Archive* GetArchive(ArchiveHandle handle) {
|
|
auto itr = handle_map.find(handle);
|
|
return (itr == handle_map.end()) ? nullptr : itr->second;
|
|
}
|
|
|
|
ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code) {
|
|
LOG_TRACE(Service_FS, "Opening archive with id code 0x%08X", id_code);
|
|
|
|
auto itr = id_code_map.find(id_code);
|
|
if (itr == id_code_map.end()) {
|
|
if (id_code == ArchiveIdCode::SaveData) {
|
|
// When a SaveData archive is created for the first time, it is not yet formatted
|
|
// and the save file/directory structure expected by the game has not yet been initialized.
|
|
// Returning the NotFormatted error code will signal the game to provision the SaveData archive
|
|
// with the files and folders that it expects.
|
|
// The FormatSaveData service call will create the SaveData archive when it is called.
|
|
return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
|
|
ErrorSummary::InvalidState, ErrorLevel::Status);
|
|
}
|
|
// TODO: Verify error against hardware
|
|
return ResultCode(ErrorDescription::NotFound, ErrorModule::FS,
|
|
ErrorSummary::NotFound, ErrorLevel::Permanent);
|
|
}
|
|
|
|
// This should never even happen in the first place with 64-bit handles,
|
|
while (handle_map.count(next_handle) != 0) {
|
|
++next_handle;
|
|
}
|
|
handle_map.emplace(next_handle, itr->second.get());
|
|
return MakeResult<ArchiveHandle>(next_handle++);
|
|
}
|
|
|
|
ResultCode CloseArchive(ArchiveHandle handle) {
|
|
if (handle_map.erase(handle) == 0)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
else
|
|
return RESULT_SUCCESS;
|
|
}
|
|
|
|
// TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in
|
|
// http://3dbrew.org/wiki/Filesystem_services#ProgramRegistry_service_.22fs:REG.22
|
|
ResultCode CreateArchive(std::unique_ptr<FileSys::ArchiveBackend>&& backend, ArchiveIdCode id_code) {
|
|
auto result = id_code_map.emplace(id_code, Common::make_unique<Archive>(std::move(backend), id_code));
|
|
|
|
bool inserted = result.second;
|
|
_dbg_assert_msg_(Service_FS, inserted, "Tried to register more than one archive with same id code");
|
|
|
|
auto& archive = result.first->second;
|
|
LOG_DEBUG(Service_FS, "Registered archive %s with id code 0x%08X", archive->GetName().c_str(), id_code);
|
|
return RESULT_SUCCESS;
|
|
}
|
|
|
|
ResultVal<Handle> OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path, const FileSys::Mode mode) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
std::unique_ptr<FileSys::FileBackend> backend = archive->backend->OpenFile(path, mode);
|
|
if (backend == nullptr) {
|
|
return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS,
|
|
ErrorSummary::NotFound, ErrorLevel::Status);
|
|
}
|
|
|
|
auto file = Common::make_unique<File>(std::move(backend), path);
|
|
// TOOD(yuriks): Fix error reporting
|
|
Handle handle = Kernel::g_handle_table.Create(file.release()).ValueOr(INVALID_HANDLE);
|
|
return MakeResult<Handle>(handle);
|
|
}
|
|
|
|
ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
if (archive->backend->DeleteFile(path))
|
|
return RESULT_SUCCESS;
|
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
}
|
|
|
|
ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, const FileSys::Path& src_path,
|
|
ArchiveHandle dest_archive_handle, const FileSys::Path& dest_path) {
|
|
Archive* src_archive = GetArchive(src_archive_handle);
|
|
Archive* dest_archive = GetArchive(dest_archive_handle);
|
|
if (src_archive == nullptr || dest_archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
if (src_archive == dest_archive) {
|
|
if (src_archive->backend->RenameFile(src_path, dest_path))
|
|
return RESULT_SUCCESS;
|
|
} else {
|
|
// TODO: Implement renaming across archives
|
|
return UnimplementedFunction(ErrorModule::FS);
|
|
}
|
|
|
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
|
// exist or similar. Verify.
|
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
|
}
|
|
|
|
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
if (archive->backend->DeleteDirectory(path))
|
|
return RESULT_SUCCESS;
|
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
}
|
|
|
|
ResultCode CreateFileInArchive(Handle archive_handle, const FileSys::Path& path, u32 file_size) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
return archive->backend->CreateFile(path, file_size);
|
|
}
|
|
|
|
ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
if (archive->backend->CreateDirectory(path))
|
|
return RESULT_SUCCESS;
|
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
ErrorSummary::Canceled, ErrorLevel::Status);
|
|
}
|
|
|
|
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, const FileSys::Path& src_path,
|
|
ArchiveHandle dest_archive_handle, const FileSys::Path& dest_path) {
|
|
Archive* src_archive = GetArchive(src_archive_handle);
|
|
Archive* dest_archive = GetArchive(dest_archive_handle);
|
|
if (src_archive == nullptr || dest_archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
if (src_archive == dest_archive) {
|
|
if (src_archive->backend->RenameDirectory(src_path, dest_path))
|
|
return RESULT_SUCCESS;
|
|
} else {
|
|
// TODO: Implement renaming across archives
|
|
return UnimplementedFunction(ErrorModule::FS);
|
|
}
|
|
|
|
// TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
|
|
// exist or similar. Verify.
|
|
return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
|
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
|
}
|
|
|
|
/**
|
|
* Open a Directory from an Archive
|
|
* @param archive_handle Handle to an open Archive object
|
|
* @param path Path to the Directory inside of the Archive
|
|
* @return Opened Directory object
|
|
*/
|
|
ResultVal<Handle> OpenDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
|
|
Archive* archive = GetArchive(archive_handle);
|
|
if (archive == nullptr)
|
|
return InvalidHandle(ErrorModule::FS);
|
|
|
|
std::unique_ptr<FileSys::DirectoryBackend> backend = archive->backend->OpenDirectory(path);
|
|
if (backend == nullptr) {
|
|
return ResultCode(ErrorDescription::NotFound, ErrorModule::FS,
|
|
ErrorSummary::NotFound, ErrorLevel::Permanent);
|
|
}
|
|
|
|
auto directory = Common::make_unique<Directory>(std::move(backend), path);
|
|
// TOOD(yuriks): Fix error reporting
|
|
Handle handle = Kernel::g_handle_table.Create(directory.release()).ValueOr(INVALID_HANDLE);
|
|
return MakeResult<Handle>(handle);
|
|
}
|
|
|
|
ResultCode FormatSaveData() {
|
|
// TODO(Subv): Actually wipe the savedata folder after creating or opening it
|
|
|
|
// Do not create the archive again if it already exists
|
|
if (id_code_map.find(ArchiveIdCode::SaveData) != id_code_map.end())
|
|
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the correct error code
|
|
|
|
// Create the SaveData archive
|
|
std::string savedata_directory = FileUtil::GetUserPath(D_SAVEDATA_IDX);
|
|
auto savedata_archive = Common::make_unique<FileSys::Archive_SaveData>(savedata_directory,
|
|
Kernel::g_program_id);
|
|
|
|
if (savedata_archive->Initialize()) {
|
|
CreateArchive(std::move(savedata_archive), ArchiveIdCode::SaveData);
|
|
return RESULT_SUCCESS;
|
|
} else {
|
|
LOG_ERROR(Service_FS, "Can't instantiate SaveData archive with path %s",
|
|
savedata_archive->GetMountPoint().c_str());
|
|
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the proper error code
|
|
}
|
|
}
|
|
|
|
/// Initialize archives
|
|
void ArchiveInit() {
|
|
next_handle = 1;
|
|
|
|
// TODO(Link Mauve): Add the other archive types (see here for the known types:
|
|
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes). Currently the only half-finished
|
|
// archive type is SDMC, so it is the only one getting exposed.
|
|
|
|
std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX);
|
|
auto sdmc_archive = Common::make_unique<FileSys::Archive_SDMC>(sdmc_directory);
|
|
if (sdmc_archive->Initialize())
|
|
CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC);
|
|
else
|
|
LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str());
|
|
}
|
|
|
|
/// Shutdown archives
|
|
void ArchiveShutdown() {
|
|
handle_map.clear();
|
|
id_code_map.clear();
|
|
}
|
|
|
|
} // namespace FS
|
|
} // namespace Service
|