Lime3DS/src/core/hle/service/fs/archive.cpp
Yuri Kunde Schlesner 7e2903cb74 Kernel: New handle manager
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.
2014-12-28 11:52:55 -02:00

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