mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-09 22:56:35 +00:00
ams_mitm: Implement emummc Nintendo folder redirection
This commit is contained in:
parent
733f2b3cdd
commit
746dbfe018
78 changed files with 2190 additions and 187 deletions
|
@ -33,7 +33,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace ams::mitm {
|
||||||
public: \
|
public: \
|
||||||
static constexpr size_t ThreadPriority = prio; \
|
static constexpr size_t ThreadPriority = prio; \
|
||||||
static constexpr size_t StackSize = ss; \
|
static constexpr size_t StackSize = ss; \
|
||||||
alignas(0x1000) static inline u8 Stack[StackSize]; \
|
alignas(os::MemoryPageSize) static inline u8 Stack[StackSize]; \
|
||||||
public: \
|
public: \
|
||||||
static void ThreadFunction(void *); \
|
static void ThreadFunction(void *); \
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,8 @@ namespace ams::mitm::bpc {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Globals. */
|
/* Globals. */
|
||||||
alignas(0x1000) u8 g_work_page[0x1000];
|
alignas(os::MemoryPageSize) u8 g_work_page[os::MemoryPageSize];
|
||||||
alignas(0x1000) u8 g_reboot_payload[IramPayloadMaxSize];
|
alignas(os::MemoryPageSize) u8 g_reboot_payload[IramPayloadMaxSize];
|
||||||
RebootType g_reboot_type = RebootType::ToRcm;
|
RebootType g_reboot_type = RebootType::ToRcm;
|
||||||
|
|
||||||
/* Helpers. */
|
/* Helpers. */
|
||||||
|
@ -54,9 +54,9 @@ namespace ams::mitm::bpc {
|
||||||
ClearIram();
|
ClearIram();
|
||||||
|
|
||||||
/* Copy in payload. */
|
/* Copy in payload. */
|
||||||
for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += 0x1000) {
|
for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += sizeof(g_work_page)) {
|
||||||
std::memcpy(g_work_page, &g_reboot_payload[ofs], std::min(sizeof(g_reboot_payload) - ofs, size_t(0x1000)));
|
std::memcpy(g_work_page, &g_reboot_payload[ofs], std::min(sizeof(g_reboot_payload) - ofs, sizeof(g_work_page)));
|
||||||
exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, 0x1000);
|
exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy in fatal error context, if relevant. */
|
/* Copy in fatal error context, if relevant. */
|
||||||
|
|
|
@ -62,13 +62,29 @@ namespace ams::mitm::fs {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result FsMitmService::OpenSdCardFileSystem(sf::Out<std::shared_ptr<IFileSystemInterface>> out) {
|
||||||
|
/* We only care about redirecting this for NS/emummc. */
|
||||||
|
R_UNLESS(this->client_info.program_id == ncm::ProgramId::Ns, sm::mitm::ResultShouldForwardToSession());
|
||||||
|
R_UNLESS(emummc::IsActive(), sm::mitm::ResultShouldForwardToSession());
|
||||||
|
|
||||||
|
/* Create a new SD card filesystem. */
|
||||||
|
FsFileSystem sd_fs;
|
||||||
|
R_TRY(fsOpenSdCardFileSystemFwd(this->forward_service.get(), &sd_fs));
|
||||||
|
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&sd_fs.s)};
|
||||||
|
|
||||||
|
/* Return output filesystem. */
|
||||||
|
std::shared_ptr<fs::fsa::IFileSystem> redir_fs = std::make_shared<fssystem::DirectoryRedirectionFileSystem>(std::make_shared<RemoteFileSystem>(sd_fs), "/Nintendo", emummc::GetNintendoDirPath());
|
||||||
|
out.SetValue(std::make_shared<IFileSystemInterface>(std::move(redir_fs), false), target_object_id);
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
Result FsMitmService::OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 _bis_partition_id) {
|
Result FsMitmService::OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 _bis_partition_id) {
|
||||||
const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id);
|
const ::FsBisPartitionId bis_partition_id = static_cast<::FsBisPartitionId>(_bis_partition_id);
|
||||||
|
|
||||||
/* Try to open a storage for the partition. */
|
/* Try to open a storage for the partition. */
|
||||||
FsStorage bis_storage;
|
FsStorage bis_storage;
|
||||||
R_TRY(fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id));
|
R_TRY(fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id));
|
||||||
const sf::cmif::DomainObjectId target_object_id{bis_storage.s.object_id};
|
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&bis_storage.s)};
|
||||||
|
|
||||||
const bool is_sysmodule = ncm::IsSystemProgramId(this->client_info.program_id);
|
const bool is_sysmodule = ncm::IsSystemProgramId(this->client_info.program_id);
|
||||||
const bool is_hbl = this->client_info.override_status.IsHbl();
|
const bool is_hbl = this->client_info.override_status.IsHbl();
|
||||||
|
@ -118,7 +134,7 @@ namespace ams::mitm::fs {
|
||||||
/* Try to open the process romfs. */
|
/* Try to open the process romfs. */
|
||||||
FsStorage data_storage;
|
FsStorage data_storage;
|
||||||
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage));
|
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage));
|
||||||
const sf::cmif::DomainObjectId target_object_id{data_storage.s.object_id};
|
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&data_storage.s)};
|
||||||
|
|
||||||
/* Try to get a storage from the cache. */
|
/* Try to get a storage from the cache. */
|
||||||
{
|
{
|
||||||
|
@ -160,7 +176,7 @@ namespace ams::mitm::fs {
|
||||||
/* Try to open the process romfs. */
|
/* Try to open the process romfs. */
|
||||||
FsStorage data_storage;
|
FsStorage data_storage;
|
||||||
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), &data_storage, static_cast<u64>(data_id), static_cast<NcmStorageId>(storage_id)));
|
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), &data_storage, static_cast<u64>(data_id), static_cast<NcmStorageId>(storage_id)));
|
||||||
const sf::cmif::DomainObjectId target_object_id{data_storage.s.object_id};
|
const sf::cmif::DomainObjectId target_object_id{serviceGetObjectId(&data_storage.s)};
|
||||||
|
|
||||||
/* Try to get a storage from the cache. */
|
/* Try to get a storage from the cache. */
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,10 +16,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <stratosphere.hpp>
|
#include <stratosphere.hpp>
|
||||||
#include "fsmitm_istorage_interface.hpp"
|
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
|
||||||
|
|
||||||
namespace ams::mitm::fs {
|
namespace ams::mitm::fs {
|
||||||
|
|
||||||
|
using IStorageInterface = ams::fssrv::impl::StorageInterfaceAdapter;
|
||||||
|
using IFileSystemInterface = ams::fssrv::impl::FileSystemInterfaceAdapter;
|
||||||
|
|
||||||
/* TODO: Consider re-enabling the mitm flag logic. */
|
/* TODO: Consider re-enabling the mitm flag logic. */
|
||||||
|
|
||||||
class FsMitmService : public sf::IMitmServiceObject {
|
class FsMitmService : public sf::IMitmServiceObject {
|
||||||
|
@ -59,7 +62,7 @@ namespace ams::mitm::fs {
|
||||||
/* Overridden commands. */
|
/* Overridden commands. */
|
||||||
/* Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 program_id, u32 filesystem_type); */
|
/* Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 program_id, u32 filesystem_type); */
|
||||||
/* Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 program_id, u32 filesystem_type); */
|
/* Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 program_id, u32 filesystem_type); */
|
||||||
/* Result OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out); */
|
Result OpenSdCardFileSystem(sf::Out<std::shared_ptr<IFileSystemInterface>> out);
|
||||||
/* Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct); */
|
/* Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct); */
|
||||||
Result OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
Result OpenBisStorage(sf::Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
||||||
Result OpenDataStorageByCurrentProcess(sf::Out<std::shared_ptr<IStorageInterface>> out);
|
Result OpenDataStorageByCurrentProcess(sf::Out<std::shared_ptr<IStorageInterface>> out);
|
||||||
|
@ -68,7 +71,7 @@ namespace ams::mitm::fs {
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */
|
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithPatch, hos::Version_200), */
|
||||||
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithId, hos::Version_200), */
|
/* MAKE_SERVICE_COMMAND_META(OpenFileSystemWithId, hos::Version_200), */
|
||||||
/* MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem), */
|
MAKE_SERVICE_COMMAND_META(OpenSdCardFileSystem),
|
||||||
/* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */
|
/* MAKE_SERVICE_COMMAND_META(OpenSaveDataFileSystem), */
|
||||||
MAKE_SERVICE_COMMAND_META(OpenBisStorage),
|
MAKE_SERVICE_COMMAND_META(OpenBisStorage),
|
||||||
MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess),
|
MAKE_SERVICE_COMMAND_META(OpenDataStorageByCurrentProcess),
|
||||||
|
|
|
@ -16,6 +16,17 @@
|
||||||
#include "fs_shim.h"
|
#include "fs_shim.h"
|
||||||
|
|
||||||
/* Missing fsp-srv commands. */
|
/* Missing fsp-srv commands. */
|
||||||
|
static Result _fsOpenSession(Service *s, Service* out, u32 cmd_id) {
|
||||||
|
return serviceDispatch(s, cmd_id,
|
||||||
|
.out_num_objects = 1,
|
||||||
|
.out_objects = out,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out) {
|
||||||
|
return _fsOpenSession(s, &out->s, 18);
|
||||||
|
}
|
||||||
|
|
||||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id) {
|
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id) {
|
||||||
const u32 tmp = partition_id;
|
const u32 tmp = partition_id;
|
||||||
return serviceDispatchIn(s, 12, tmp,
|
return serviceDispatchIn(s, 12, tmp,
|
||||||
|
@ -25,10 +36,7 @@ Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partitio
|
||||||
}
|
}
|
||||||
|
|
||||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
|
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
|
||||||
return serviceDispatch(s, 200,
|
return _fsOpenSession(s, &out->s, 200);
|
||||||
.out_num_objects = 1,
|
|
||||||
.out_objects = &out->s,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id) {
|
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Missing fsp-srv commands. */
|
/* Missing fsp-srv commands. */
|
||||||
|
Result fsOpenSdCardFileSystemFwd(Service* s, FsFileSystem* out);
|
||||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id);
|
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisPartitionId partition_id);
|
||||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
||||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id);
|
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorage* out, u64 data_id, NcmStorageId storage_id);
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
namespace ams::mitm::fs {
|
|
||||||
|
|
||||||
class IStorageInterface : public sf::IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Flush = 2,
|
|
||||||
SetSize = 3,
|
|
||||||
GetSize = 4,
|
|
||||||
OperateRange = 5,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
std::unique_ptr<ams::fs::IStorage> base_storage;
|
|
||||||
public:
|
|
||||||
IStorageInterface(ams::fs::IStorage *s) : base_storage(s) { /* ... */ }
|
|
||||||
IStorageInterface(std::unique_ptr<ams::fs::IStorage> s) : base_storage(std::move(s)) { /* ... */ }
|
|
||||||
private:
|
|
||||||
/* Command API. */
|
|
||||||
virtual Result Read(s64 offset, const sf::OutNonSecureBuffer &buffer, s64 size) final;
|
|
||||||
virtual Result Write(s64 offset, const sf::InNonSecureBuffer &buffer, s64 size) final;
|
|
||||||
virtual Result Flush() final;
|
|
||||||
virtual Result SetSize(s64 size) final;
|
|
||||||
virtual Result GetSize(sf::Out<s64> out) final;
|
|
||||||
virtual Result OperateRange(sf::Out<ams::fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) final;
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* 1.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(Read),
|
|
||||||
MAKE_SERVICE_COMMAND_META(Write),
|
|
||||||
MAKE_SERVICE_COMMAND_META(Flush),
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetSize),
|
|
||||||
MAKE_SERVICE_COMMAND_META(GetSize),
|
|
||||||
|
|
||||||
/* 4.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(OperateRange, hos::Version_400),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -34,7 +34,7 @@ namespace ams::boot {
|
||||||
|
|
||||||
/* Helpers. */
|
/* Helpers. */
|
||||||
bool IsUsbClockValid() {
|
bool IsUsbClockValid() {
|
||||||
uintptr_t car_regs = dd::GetIoMapping(0x60006000ul, 0x1000);
|
uintptr_t car_regs = dd::GetIoMapping(0x60006000ul, os::MemoryPageSize);
|
||||||
|
|
||||||
const u32 pllu = reg::Read(car_regs + 0xC0);
|
const u32 pllu = reg::Read(car_regs + 0xC0);
|
||||||
const u32 utmip = reg::Read(car_regs + 0x480);
|
const u32 utmip = reg::Read(car_regs + 0x480);
|
||||||
|
|
|
@ -42,18 +42,19 @@ namespace ams::boot {
|
||||||
constexpr size_t FrameBufferHeight = 1280;
|
constexpr size_t FrameBufferHeight = 1280;
|
||||||
constexpr size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32);
|
constexpr size_t FrameBufferSize = FrameBufferHeight * FrameBufferWidth * sizeof(u32);
|
||||||
|
|
||||||
constexpr uintptr_t Disp1Base = 0x54200000ul;
|
constexpr uintptr_t Disp1Base = 0x54200000ul;
|
||||||
constexpr uintptr_t DsiBase = 0x54300000ul;
|
constexpr uintptr_t DsiBase = 0x54300000ul;
|
||||||
constexpr uintptr_t ClkRstBase = 0x60006000ul;
|
constexpr uintptr_t ClkRstBase = 0x60006000ul;
|
||||||
constexpr uintptr_t GpioBase = 0x6000D000ul;
|
constexpr uintptr_t GpioBase = 0x6000D000ul;
|
||||||
constexpr uintptr_t ApbMiscBase = 0x70000000ul;
|
constexpr uintptr_t ApbMiscBase = 0x70000000ul;
|
||||||
constexpr uintptr_t MipiCalBase = 0x700E3000ul;
|
constexpr uintptr_t MipiCalBase = 0x700E3000ul;
|
||||||
constexpr size_t Disp1Size = 0x3000;
|
|
||||||
constexpr size_t DsiSize = 0x1000;
|
constexpr size_t Disp1Size = 3 * os::MemoryPageSize;
|
||||||
constexpr size_t ClkRstSize = 0x1000;
|
constexpr size_t DsiSize = os::MemoryPageSize;
|
||||||
constexpr size_t GpioSize = 0x1000;
|
constexpr size_t ClkRstSize = os::MemoryPageSize;
|
||||||
constexpr size_t ApbMiscSize = 0x1000;
|
constexpr size_t GpioSize = os::MemoryPageSize;
|
||||||
constexpr size_t MipiCalSize = 0x1000;
|
constexpr size_t ApbMiscSize = os::MemoryPageSize;
|
||||||
|
constexpr size_t MipiCalSize = os::MemoryPageSize;
|
||||||
|
|
||||||
/* Types. */
|
/* Types. */
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace ams::boot {
|
||||||
constexpr size_t IramPayloadMaxSize = 0x2E000;
|
constexpr size_t IramPayloadMaxSize = 0x2E000;
|
||||||
|
|
||||||
/* Globals. */
|
/* Globals. */
|
||||||
u8 __attribute__ ((aligned (0x1000))) g_work_page[0x1000];
|
alignas(os::MemoryPageSize) u8 g_work_page[os::MemoryPageSize];
|
||||||
|
|
||||||
/* Helpers. */
|
/* Helpers. */
|
||||||
void ClearIram() {
|
void ClearIram() {
|
||||||
|
@ -45,9 +45,9 @@ namespace ams::boot {
|
||||||
ClearIram();
|
ClearIram();
|
||||||
|
|
||||||
/* Copy in payload. */
|
/* Copy in payload. */
|
||||||
for (size_t ofs = 0; ofs < fusee_primary_bin_size; ofs += 0x1000) {
|
for (size_t ofs = 0; ofs < fusee_primary_bin_size; ofs += sizeof(g_work_page)) {
|
||||||
std::memcpy(g_work_page, &fusee_primary_bin[ofs], std::min(static_cast<size_t>(fusee_primary_bin_size - ofs), size_t(0x1000)));
|
std::memcpy(g_work_page, &fusee_primary_bin[ofs], std::min(static_cast<size_t>(fusee_primary_bin_size - ofs), sizeof(g_work_page)));
|
||||||
exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, 0x1000);
|
exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace ams::boot {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/* Globals. */
|
/* Globals. */
|
||||||
alignas(0x1000) u8 g_boot_image_work_buffer[0x10000];
|
alignas(os::MemoryPageSize) u8 g_boot_image_work_buffer[0x10000];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace ams::gpio {
|
||||||
|
|
||||||
uintptr_t GetBaseAddress() {
|
uintptr_t GetBaseAddress() {
|
||||||
if (!g_initialized_gpio_vaddr) {
|
if (!g_initialized_gpio_vaddr) {
|
||||||
g_gpio_vaddr = dd::GetIoMapping(PhysicalBase, 0x1000);
|
g_gpio_vaddr = dd::GetIoMapping(PhysicalBase, os::MemoryPageSize);
|
||||||
g_initialized_gpio_vaddr = true;
|
g_initialized_gpio_vaddr = true;
|
||||||
}
|
}
|
||||||
return g_gpio_vaddr;
|
return g_gpio_vaddr;
|
||||||
|
|
|
@ -83,7 +83,7 @@ namespace ams::i2c::driver::impl {
|
||||||
12, 22, 3, 7, 15, 6
|
12, 22, 3, 7, 15, 6
|
||||||
};
|
};
|
||||||
|
|
||||||
const uintptr_t registers = dd::GetIoMapping(0x60006000ul, 0x1000);
|
const uintptr_t registers = dd::GetIoMapping(0x60006000ul, os::MemoryPageSize);
|
||||||
const size_t idx = ConvertToIndex(bus);
|
const size_t idx = ConvertToIndex(bus);
|
||||||
this->clk_src_reg = registers + s_clk_src_offsets[idx];
|
this->clk_src_reg = registers + s_clk_src_offsets[idx];
|
||||||
this->clk_en_reg = registers + s_clk_en_offsets[idx];
|
this->clk_en_reg = registers + s_clk_en_offsets[idx];
|
||||||
|
@ -97,7 +97,7 @@ namespace ams::i2c::driver::impl {
|
||||||
0x0000, 0x0400, 0x0500, 0x0700, 0x1000, 0x1100
|
0x0000, 0x0400, 0x0500, 0x0700, 0x1000, 0x1100
|
||||||
};
|
};
|
||||||
|
|
||||||
const uintptr_t registers = dd::GetIoMapping(0x7000c000ul, 0x2000) + s_offsets[ConvertToIndex(bus)];
|
const uintptr_t registers = dd::GetIoMapping(0x7000c000ul, 2 * os::MemoryPageSize) + s_offsets[ConvertToIndex(bus)];
|
||||||
return reinterpret_cast<Registers *>(registers);
|
return reinterpret_cast<Registers *>(registers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace ams::creport {
|
||||||
|
|
||||||
class CrashReport {
|
class CrashReport {
|
||||||
private:
|
private:
|
||||||
static constexpr size_t DyingMessageSizeMax = 0x1000;
|
static constexpr size_t DyingMessageSizeMax = os::MemoryPageSize;
|
||||||
private:
|
private:
|
||||||
Handle debug_handle = INVALID_HANDLE;
|
Handle debug_handle = INVALID_HANDLE;
|
||||||
bool has_extra_info = true;
|
bool has_extra_info = true;
|
||||||
|
|
|
@ -33,7 +33,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,7 +229,7 @@ namespace ams::creport {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We want to read the last two pages of .rodata. */
|
/* We want to read the last two pages of .rodata. */
|
||||||
static u8 s_last_rodata_pages[2 * 0x1000];
|
static u8 s_last_rodata_pages[2 * os::MemoryPageSize];
|
||||||
const size_t read_size = mi.size >= sizeof(s_last_rodata_pages) ? sizeof(s_last_rodata_pages) : (sizeof(s_last_rodata_pages) / 2);
|
const size_t read_size = mi.size >= sizeof(s_last_rodata_pages) ? sizeof(s_last_rodata_pages) : (sizeof(s_last_rodata_pages) / 2);
|
||||||
if (R_FAILED(svcReadDebugProcessMemory(s_last_rodata_pages, this->debug_handle, mi.addr + mi.size - read_size, read_size))) {
|
if (R_FAILED(svcReadDebugProcessMemory(s_last_rodata_pages, this->debug_handle, mi.addr + mi.size - read_size, read_size))) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -50,9 +50,9 @@ namespace ams::dmnt::cheat::impl {
|
||||||
CheatEntry cheat_entries[MaxCheatCount] = {};
|
CheatEntry cheat_entries[MaxCheatCount] = {};
|
||||||
std::map<u64, FrozenAddressValue> frozen_addresses_map;
|
std::map<u64, FrozenAddressValue> frozen_addresses_map;
|
||||||
|
|
||||||
alignas(0x1000) u8 detect_thread_stack[ThreadStackSize] = {};
|
alignas(os::MemoryPageSize) u8 detect_thread_stack[ThreadStackSize] = {};
|
||||||
alignas(0x1000) u8 debug_events_thread_stack[ThreadStackSize] = {};
|
alignas(os::MemoryPageSize) u8 debug_events_thread_stack[ThreadStackSize] = {};
|
||||||
alignas(0x1000) u8 vm_thread_stack[ThreadStackSize] = {};
|
alignas(os::MemoryPageSize) u8 vm_thread_stack[ThreadStackSize] = {};
|
||||||
private:
|
private:
|
||||||
static void DetectLaunchThread(void *_this);
|
static void DetectLaunchThread(void *_this);
|
||||||
static void VirtualMachineThread(void *_this);
|
static void VirtualMachineThread(void *_this);
|
||||||
|
|
|
@ -24,14 +24,14 @@ namespace ams::dmnt::cheat::impl {
|
||||||
class DebugEventsManager {
|
class DebugEventsManager {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t NumCores = 4;
|
static constexpr size_t NumCores = 4;
|
||||||
static constexpr size_t ThreadStackSize = 0x1000;
|
static constexpr size_t ThreadStackSize = os::MemoryPageSize;
|
||||||
static constexpr size_t ThreadPriority = 24;
|
static constexpr size_t ThreadPriority = 24;
|
||||||
private:
|
private:
|
||||||
std::array<os::MessageQueue, NumCores> message_queues;
|
std::array<os::MessageQueue, NumCores> message_queues;
|
||||||
std::array<os::Thread, NumCores> threads;
|
std::array<os::Thread, NumCores> threads;
|
||||||
os::Event continued_event;
|
os::Event continued_event;
|
||||||
|
|
||||||
alignas(0x1000) u8 thread_stacks[NumCores][ThreadStackSize];
|
alignas(os::MemoryPageSize) u8 thread_stacks[NumCores][ThreadStackSize];
|
||||||
private:
|
private:
|
||||||
static void PerCoreThreadFunction(void *_this) {
|
static void PerCoreThreadFunction(void *_this) {
|
||||||
/* This thread will wait on the appropriate message queue. */
|
/* This thread will wait on the appropriate message queue. */
|
||||||
|
|
|
@ -122,7 +122,7 @@ namespace {
|
||||||
static_assert(TotalThreads >= 1, "TotalThreads");
|
static_assert(TotalThreads >= 1, "TotalThreads");
|
||||||
constexpr size_t NumExtraThreads = TotalThreads - 1;
|
constexpr size_t NumExtraThreads = TotalThreads - 1;
|
||||||
constexpr size_t ThreadStackSize = 0x4000;
|
constexpr size_t ThreadStackSize = 0x4000;
|
||||||
alignas(0x1000) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize];
|
alignas(os::MemoryPageSize) u8 g_extra_thread_stacks[NumExtraThreads][ThreadStackSize];
|
||||||
|
|
||||||
os::Thread g_extra_threads[NumExtraThreads];
|
os::Thread g_extra_threads[NumExtraThreads];
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@ namespace ams::fatal::srv {
|
||||||
class ITaskWithStack : public ITask {
|
class ITaskWithStack : public ITask {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t StackSize = _StackSize;
|
static constexpr size_t StackSize = _StackSize;
|
||||||
static_assert(util::IsAligned(StackSize, 0x1000), "StackSize alignment");
|
static_assert(util::IsAligned(StackSize, os::MemoryPageSize), "StackSize alignment");
|
||||||
protected:
|
protected:
|
||||||
alignas(0x1000) u8 stack_mem[StackSize] = {};
|
alignas(os::MemoryPageSize) u8 stack_mem[StackSize] = {};
|
||||||
public:
|
public:
|
||||||
virtual u8 *GetStack() override final {
|
virtual u8 *GetStack() override final {
|
||||||
return this->stack_mem;
|
return this->stack_mem;
|
||||||
|
|
|
@ -16,7 +16,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||||
# INCLUDES is a list of directories containing header files
|
# INCLUDES is a list of directories containing header files
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
TARGET := $(notdir $(CURDIR))
|
TARGET := $(notdir $(CURDIR))
|
||||||
SOURCES := source source/ams source/hos source/result source/os source/os/impl source/dd source/fs source/sf source/sf/cmif source/sf/hipc source/dmnt source/spl source/spl/smc source/updater source/patcher source/map source/rnd source/util source/sm source/cfg source/pm source/hid source/ldr source/kvdb source/settings source/boot2
|
SOURCES ?= $(shell find source -type d)
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include
|
INCLUDES := include
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
|
@ -50,6 +50,8 @@ namespace ams::fs {
|
||||||
R_DEFINE_ERROR_RANGE(AllocationFailure, 3200, 3499);
|
R_DEFINE_ERROR_RANGE(AllocationFailure, 3200, 3499);
|
||||||
R_DEFINE_ERROR_RESULT(AllocationFailureInDirectorySaveDataFileSystem, 3321);
|
R_DEFINE_ERROR_RESULT(AllocationFailureInDirectorySaveDataFileSystem, 3321);
|
||||||
R_DEFINE_ERROR_RESULT(AllocationFailureInSubDirectoryFileSystem, 3355);
|
R_DEFINE_ERROR_RESULT(AllocationFailureInSubDirectoryFileSystem, 3355);
|
||||||
|
R_DEFINE_ERROR_RESULT(AllocationFailureInPathNormalizer, 3367);
|
||||||
|
R_DEFINE_ERROR_RESULT(AllocationFailureInFileSystemInterfaceAdapter, 3407);
|
||||||
|
|
||||||
R_DEFINE_ERROR_RANGE(MmcAccessFailed, 3500, 3999);
|
R_DEFINE_ERROR_RANGE(MmcAccessFailed, 3500, 3999);
|
||||||
|
|
||||||
|
@ -79,6 +81,12 @@ namespace ams::fs {
|
||||||
R_DEFINE_ERROR_RESULT(DirectoryUnobtainable, 6006);
|
R_DEFINE_ERROR_RESULT(DirectoryUnobtainable, 6006);
|
||||||
R_DEFINE_ERROR_RESULT(NotNormalized, 6007);
|
R_DEFINE_ERROR_RESULT(NotNormalized, 6007);
|
||||||
|
|
||||||
|
R_DEFINE_ERROR_RANGE(InvalidPathForOperation, 6030, 6059);
|
||||||
|
R_DEFINE_ERROR_RESULT(DirectoryNotDeletable, 6031);
|
||||||
|
R_DEFINE_ERROR_RESULT(DirectoryNotRenamable, 6032);
|
||||||
|
R_DEFINE_ERROR_RESULT(IncompatiblePath, 6033);
|
||||||
|
R_DEFINE_ERROR_RESULT(RenameToOtherFileSystem, 6034);
|
||||||
|
|
||||||
R_DEFINE_ERROR_RESULT(InvalidOffset, 6061);
|
R_DEFINE_ERROR_RESULT(InvalidOffset, 6061);
|
||||||
R_DEFINE_ERROR_RESULT(InvalidSize, 6062);
|
R_DEFINE_ERROR_RESULT(InvalidSize, 6062);
|
||||||
R_DEFINE_ERROR_RESULT(NullptrArgument, 6063);
|
R_DEFINE_ERROR_RESULT(NullptrArgument, 6063);
|
||||||
|
|
|
@ -51,3 +51,5 @@
|
||||||
|
|
||||||
/* Include FS last. */
|
/* Include FS last. */
|
||||||
#include "stratosphere/fs.hpp"
|
#include "stratosphere/fs.hpp"
|
||||||
|
#include "stratosphere/fssrv.hpp"
|
||||||
|
#include "stratosphere/fssystem.hpp"
|
||||||
|
|
|
@ -24,3 +24,5 @@
|
||||||
#include "fs/fs_remote_storage.hpp"
|
#include "fs/fs_remote_storage.hpp"
|
||||||
#include "fs/fs_file_storage.hpp"
|
#include "fs/fs_file_storage.hpp"
|
||||||
#include "fs/fs_query_range.hpp"
|
#include "fs/fs_query_range.hpp"
|
||||||
|
#include "fs/fs_path_tool.hpp"
|
||||||
|
#include "fs/fs_path_utils.hpp"
|
||||||
|
|
|
@ -18,3 +18,10 @@
|
||||||
#include "../os.hpp"
|
#include "../os.hpp"
|
||||||
#include "../ncm.hpp"
|
#include "../ncm.hpp"
|
||||||
#include "../sf.hpp"
|
#include "../sf.hpp"
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
/* TODO: Better place for this? */
|
||||||
|
constexpr inline size_t MountNameLengthMax = 15;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
namespace ams::fs {
|
namespace ams::fs {
|
||||||
|
|
||||||
|
constexpr inline size_t EntryNameLengthMax = 0x300;
|
||||||
|
|
||||||
using DirectoryEntry = ::FsDirectoryEntry;
|
using DirectoryEntry = ::FsDirectoryEntry;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "fs_common.hpp"
|
||||||
|
#include "../fssrv/fssrv_sf_path.hpp"
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
namespace StringTraits {
|
||||||
|
|
||||||
|
constexpr inline char DirectorySeparator = '/';
|
||||||
|
constexpr inline char DriveSeparator = ':';
|
||||||
|
constexpr inline char Dot = '.';
|
||||||
|
constexpr inline char NullTerminator = '\x00';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class PathTool {
|
||||||
|
public:
|
||||||
|
static constexpr fssrv::sf::Path RootPath = fssrv::sf::FspPath::Encode("/");
|
||||||
|
public:
|
||||||
|
static constexpr inline bool IsSeparator(char c) {
|
||||||
|
return c == StringTraits::DirectorySeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsNullTerminator(char c) {
|
||||||
|
return c == StringTraits::NullTerminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsDot(char c) {
|
||||||
|
return c == StringTraits::Dot;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsWindowsDriveCharacter(char c) {
|
||||||
|
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsDriveSeparator(char c) {
|
||||||
|
return c == StringTraits::DriveSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsWindowsAbsolutePath(const char *p) {
|
||||||
|
return IsWindowsDriveCharacter(p[0]) && IsDriveSeparator(p[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsCurrentDirectory(const char *p) {
|
||||||
|
return IsDot(p[0]) && (IsSeparator(p[1]) || IsNullTerminator(p[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr inline bool IsParentDirectory(const char *p) {
|
||||||
|
return IsDot(p[0]) && IsDot(p[1]) && (IsSeparator(p[2]) || IsNullTerminator(p[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Result Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved = false);
|
||||||
|
static Result IsNormalized(bool *out, const char *path);
|
||||||
|
|
||||||
|
static bool IsSubPath(const char *lhs, const char *rhs);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "fs_common.hpp"
|
||||||
|
#include "../fssrv/fssrv_sf_path.hpp"
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
inline void Replace(char *dst, size_t dst_size, char old_char, char new_char) {
|
||||||
|
for (char *cur = dst; cur < dst + dst_size && *cur != '\x00'; cur++) {
|
||||||
|
if (*cur == old_char) {
|
||||||
|
*cur = new_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Result FspPathPrintf(fssrv::sf::FspPath *dst, const char *format, ...) {
|
||||||
|
/* Format the path. */
|
||||||
|
std::va_list va_list;
|
||||||
|
va_start(va_list, format);
|
||||||
|
const size_t len = std::vsnprintf(dst->str, sizeof(dst->str), format, va_list);
|
||||||
|
va_end(va_list);
|
||||||
|
|
||||||
|
/* Validate length. */
|
||||||
|
R_UNLESS(len < sizeof(dst->str), fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Fix slashes. */
|
||||||
|
Replace(dst->str, sizeof(dst->str) - 1, '\\', '/');
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len);
|
||||||
|
|
||||||
|
}
|
|
@ -57,6 +57,10 @@ namespace ams::fs {
|
||||||
/* TODO: How should this be handled? */
|
/* TODO: How should this be handled? */
|
||||||
return fs::ResultNotImplemented();
|
return fs::ResultNotImplemented();
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
||||||
|
return sf::cmif::DomainObjectId{serviceGetObjectId(&this->base_file->s)};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class RemoteDirectory : public fsa::IDirectory {
|
class RemoteDirectory : public fsa::IDirectory {
|
||||||
|
@ -78,6 +82,10 @@ namespace ams::fs {
|
||||||
virtual Result GetEntryCountImpl(s64 *out) override final {
|
virtual Result GetEntryCountImpl(s64 *out) override final {
|
||||||
return fsDirGetEntryCount(this->base_dir.get(), out);
|
return fsDirGetEntryCount(this->base_dir.get(), out);
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
|
||||||
|
return sf::cmif::DomainObjectId{serviceGetObjectId(&this->base_dir->s)};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class RemoteFileSystem : public fsa::IFileSystem {
|
class RemoteFileSystem : public fsa::IFileSystem {
|
||||||
|
|
|
@ -39,6 +39,9 @@ namespace ams::fs::fsa {
|
||||||
R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
|
R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
|
||||||
return this->GetEntryCountImpl(out);
|
return this->GetEntryCountImpl(out);
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
/* TODO: This is a hack to allow the mitm API to work. Find a better way? */
|
||||||
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const = 0;
|
||||||
protected:
|
protected:
|
||||||
/* ...? */
|
/* ...? */
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -78,6 +78,9 @@ namespace ams::fs::fsa {
|
||||||
Result OperateRange(OperationId op_id, s64 offset, s64 size) {
|
Result OperateRange(OperationId op_id, s64 offset, s64 size) {
|
||||||
return this->OperateRangeImpl(nullptr, 0, op_id, offset, size, nullptr, 0);
|
return this->OperateRangeImpl(nullptr, 0, op_id, offset, size, nullptr, 0);
|
||||||
}
|
}
|
||||||
|
public:
|
||||||
|
/* TODO: This is a hack to allow the mitm API to work. Find a better way? */
|
||||||
|
virtual sf::cmif::DomainObjectId GetDomainObjectId() const = 0;
|
||||||
protected:
|
protected:
|
||||||
/* ...? */
|
/* ...? */
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -151,9 +151,9 @@ namespace ams::fs::fsa {
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const char *path) = 0;
|
virtual Result DeleteDirectoryRecursivelyImpl(const char *path) = 0;
|
||||||
virtual Result RenameFileImpl(const char *old_path, const char *new_path) = 0;
|
virtual Result RenameFileImpl(const char *old_path, const char *new_path) = 0;
|
||||||
virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) = 0;
|
virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) = 0;
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const char *path) = 0;
|
virtual Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) = 0;
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> *out_file, const char *path, OpenMode mode) = 0;
|
virtual Result OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) = 0;
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> *out_dir, const char *path, OpenDirectoryMode mode) = 0;
|
virtual Result OpenDirectoryImpl(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const char *path, fs::OpenDirectoryMode mode) = 0;
|
||||||
virtual Result CommitImpl() = 0;
|
virtual Result CommitImpl() = 0;
|
||||||
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) {
|
virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) {
|
||||||
|
@ -166,11 +166,11 @@ namespace ams::fs::fsa {
|
||||||
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const char *path) = 0;
|
virtual Result CleanDirectoryRecursivelyImpl(const char *path) = 0;
|
||||||
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FileTimeStampRaw *out, const char *path) {
|
virtual Result GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) {
|
||||||
return fs::ResultNotImplemented();
|
return fs::ResultNotImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Result QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, QueryId query, const char *path) {
|
virtual Result QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) {
|
||||||
return fs::ResultNotImplemented();
|
return fs::ResultNotImplemented();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
stratosphere/libstratosphere/include/stratosphere/fssrv.hpp
Normal file
19
stratosphere/libstratosphere/include/stratosphere/fssrv.hpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "fssrv/fssrv_sf_path.hpp"
|
||||||
|
#include "fssrv/fssrv_path_normalizer.hpp"
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "interface_adapters/fssrv_storage_interface_adapter.hpp"
|
||||||
|
#include "interface_adapters/fssrv_filesystem_interface_adapter.hpp"
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../fs/fs_common.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssrv {
|
||||||
|
|
||||||
|
/* This is in fssrv::detail in official code. */
|
||||||
|
/* TODO: Consider moving to ::impl? */
|
||||||
|
|
||||||
|
class PathNormalizer {
|
||||||
|
public:
|
||||||
|
enum Option : u32 {
|
||||||
|
Option_None = BIT(0),
|
||||||
|
Option_PreserveUnc = BIT(1),
|
||||||
|
Option_PreserveTailSeparator = BIT(2),
|
||||||
|
Option_HasMountName = BIT(3),
|
||||||
|
Option_AcceptEmpty = BIT(4),
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
using Buffer = std::unique_ptr<char[]>;
|
||||||
|
private:
|
||||||
|
Buffer buffer;
|
||||||
|
const char *path;
|
||||||
|
Result result;
|
||||||
|
private:
|
||||||
|
static Result Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name);
|
||||||
|
public:
|
||||||
|
explicit PathNormalizer(const char *p) : buffer(), path(nullptr), result(ResultSuccess()) {
|
||||||
|
this->result = Normalize(&this->path, &this->buffer, p, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathNormalizer(const char *p, u32 option) : buffer(), path(nullptr), result(ResultSuccess()) {
|
||||||
|
if ((option & Option_AcceptEmpty) && p[0] == '\x00') {
|
||||||
|
this->path = path;
|
||||||
|
} else {
|
||||||
|
const bool preserve_unc = (option & Option_PreserveUnc);
|
||||||
|
const bool preserve_tail_sep = (option & Option_PreserveTailSeparator);
|
||||||
|
const bool has_mount_name = (option & Option_HasMountName);
|
||||||
|
this->result = Normalize(&this->path, &this->buffer, p, preserve_unc, preserve_tail_sep, has_mount_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Result GetResult() const {
|
||||||
|
return this->result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const char * GetPath() const {
|
||||||
|
return this->path;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../fs/fs_common.hpp"
|
||||||
|
#include "../fs/fs_directory.hpp"
|
||||||
|
#include "../sf/sf_buffer_tags.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssrv::sf {
|
||||||
|
|
||||||
|
struct Path : ams::sf::LargeData {
|
||||||
|
char str[fs::EntryNameLengthMax + 1];
|
||||||
|
|
||||||
|
static constexpr Path Encode(const char *p) {
|
||||||
|
Path path = {};
|
||||||
|
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
||||||
|
path.str[i] = p[i];
|
||||||
|
if (p[i] == '\x00') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t GetPathLength(const Path &path) {
|
||||||
|
size_t len = 0;
|
||||||
|
for (size_t i = 0; i < sizeof(path) - 1 && path.str[i] != '\x00'; i++) {
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(std::is_pod<Path>::value && sizeof(Path) == FS_MAX_PATH);
|
||||||
|
|
||||||
|
using FspPath = Path;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../../fs/fs_common.hpp"
|
||||||
|
#include "../../fs/fs_file.hpp"
|
||||||
|
#include "../../fs/fs_directory.hpp"
|
||||||
|
#include "../../fs/fs_filesystem.hpp"
|
||||||
|
#include "../../fs/fs_query_range.hpp"
|
||||||
|
#include "../../fssrv/fssrv_sf_path.hpp"
|
||||||
|
#include "../../fssystem/fssystem_utility.hpp"
|
||||||
|
|
||||||
|
namespace ams::fs::fsa {
|
||||||
|
|
||||||
|
class IFile;
|
||||||
|
class IDirectory;
|
||||||
|
class IFileSystem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ams::fssrv::impl {
|
||||||
|
|
||||||
|
class FileSystemInterfaceAdapter;
|
||||||
|
|
||||||
|
class FileInterfaceAdapter final : public ams::sf::IServiceObject {
|
||||||
|
NON_COPYABLE(FileInterfaceAdapter);
|
||||||
|
public:
|
||||||
|
enum class CommandId {
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Flush = 2,
|
||||||
|
SetSize = 3,
|
||||||
|
GetSize = 4,
|
||||||
|
OperateRange = 5,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::shared_ptr<FileSystemInterfaceAdapter> parent_filesystem;
|
||||||
|
std::unique_ptr<fs::fsa::IFile> base_file;
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
|
||||||
|
public:
|
||||||
|
FileInterfaceAdapter(std::unique_ptr<fs::fsa::IFile> &&file, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema);
|
||||||
|
~FileInterfaceAdapter();
|
||||||
|
private:
|
||||||
|
void InvalidateCache();
|
||||||
|
public:
|
||||||
|
/* Command API. */
|
||||||
|
Result Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option);
|
||||||
|
Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option);
|
||||||
|
Result Flush();
|
||||||
|
Result SetSize(s64 size);
|
||||||
|
Result GetSize(ams::sf::Out<s64> out);
|
||||||
|
Result OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
/* 1.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(Read),
|
||||||
|
MAKE_SERVICE_COMMAND_META(Write),
|
||||||
|
MAKE_SERVICE_COMMAND_META(Flush),
|
||||||
|
MAKE_SERVICE_COMMAND_META(SetSize),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetSize),
|
||||||
|
|
||||||
|
/* 4.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(OperateRange, hos::Version_400),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class DirectoryInterfaceAdapter final : public ams::sf::IServiceObject {
|
||||||
|
NON_COPYABLE(DirectoryInterfaceAdapter);
|
||||||
|
public:
|
||||||
|
enum class CommandId {
|
||||||
|
Read = 0,
|
||||||
|
GetEntryCount = 1,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::shared_ptr<FileSystemInterfaceAdapter> parent_filesystem;
|
||||||
|
std::unique_ptr<fs::fsa::IDirectory> base_dir;
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
|
||||||
|
public:
|
||||||
|
DirectoryInterfaceAdapter(std::unique_ptr<fs::fsa::IDirectory> &&dir, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema);
|
||||||
|
~DirectoryInterfaceAdapter();
|
||||||
|
public:
|
||||||
|
/* Command API */
|
||||||
|
Result Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries);
|
||||||
|
Result GetEntryCount(ams::sf::Out<s64> out);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
MAKE_SERVICE_COMMAND_META(Read),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetEntryCount),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileSystemInterfaceAdapter final : std::enable_shared_from_this<FileSystemInterfaceAdapter>, public ams::sf::IServiceObject {
|
||||||
|
NON_COPYABLE(FileSystemInterfaceAdapter);
|
||||||
|
public:
|
||||||
|
enum class CommandId {
|
||||||
|
/* 1.0.0+ */
|
||||||
|
CreateFile = 0,
|
||||||
|
DeleteFile = 1,
|
||||||
|
CreateDirectory = 2,
|
||||||
|
DeleteDirectory = 3,
|
||||||
|
DeleteDirectoryRecursively = 4,
|
||||||
|
RenameFile = 5,
|
||||||
|
RenameDirectory = 6,
|
||||||
|
GetEntryType = 7,
|
||||||
|
OpenFile = 8,
|
||||||
|
OpenDirectory = 9,
|
||||||
|
Commit = 10,
|
||||||
|
GetFreeSpaceSize = 11,
|
||||||
|
GetTotalSpaceSize = 12,
|
||||||
|
|
||||||
|
/* 3.0.0+ */
|
||||||
|
CleanDirectoryRecursively = 13,
|
||||||
|
GetFileTimeStampRaw = 14,
|
||||||
|
|
||||||
|
/* 4.0.0+ */
|
||||||
|
QueryEntry = 15,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
std::shared_ptr<fs::fsa::IFileSystem> base_fs;
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> mount_count_semaphore;
|
||||||
|
os::ReadWriteLock invalidation_lock;
|
||||||
|
bool open_count_limited;
|
||||||
|
bool deep_retry_enabled = false;
|
||||||
|
public:
|
||||||
|
FileSystemInterfaceAdapter(std::shared_ptr<fs::fsa::IFileSystem> &&fs, bool open_limited);
|
||||||
|
/* TODO: Other constructors. */
|
||||||
|
|
||||||
|
~FileSystemInterfaceAdapter();
|
||||||
|
public:
|
||||||
|
bool IsDeepRetryEnabled() const;
|
||||||
|
bool IsAccessFailureDetectionObserved() const;
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> AcquireCacheInvalidationReadLock();
|
||||||
|
os::ReadWriteLock &GetReadWriteLockForCacheInvalidation();
|
||||||
|
public:
|
||||||
|
/* Command API. */
|
||||||
|
Result CreateFile(const fssrv::sf::Path &path, s64 size, s32 option);
|
||||||
|
Result DeleteFile(const fssrv::sf::Path &path);
|
||||||
|
Result CreateDirectory(const fssrv::sf::Path &path);
|
||||||
|
Result DeleteDirectory(const fssrv::sf::Path &path);
|
||||||
|
Result DeleteDirectoryRecursively(const fssrv::sf::Path &path);
|
||||||
|
Result RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path);
|
||||||
|
Result RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path);
|
||||||
|
Result GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path);
|
||||||
|
Result OpenFile(ams::sf::Out<std::shared_ptr<FileInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode);
|
||||||
|
Result OpenDirectory(ams::sf::Out<std::shared_ptr<DirectoryInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode);
|
||||||
|
Result Commit();
|
||||||
|
Result GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path);
|
||||||
|
Result GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path);
|
||||||
|
|
||||||
|
Result CleanDirectoryRecursively(const fssrv::sf::Path &path);
|
||||||
|
Result GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path);
|
||||||
|
|
||||||
|
Result QueryEntry(const ams::sf::OutBuffer &out_buf, const ams::sf::InBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
/* 1.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(CreateFile),
|
||||||
|
MAKE_SERVICE_COMMAND_META(DeleteFile),
|
||||||
|
MAKE_SERVICE_COMMAND_META(CreateDirectory),
|
||||||
|
MAKE_SERVICE_COMMAND_META(DeleteDirectory),
|
||||||
|
MAKE_SERVICE_COMMAND_META(DeleteDirectoryRecursively),
|
||||||
|
MAKE_SERVICE_COMMAND_META(RenameFile),
|
||||||
|
MAKE_SERVICE_COMMAND_META(RenameDirectory),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetEntryType),
|
||||||
|
MAKE_SERVICE_COMMAND_META(OpenFile),
|
||||||
|
MAKE_SERVICE_COMMAND_META(OpenDirectory),
|
||||||
|
MAKE_SERVICE_COMMAND_META(Commit),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetFreeSpaceSize),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetTotalSpaceSize),
|
||||||
|
|
||||||
|
/* 3.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(CleanDirectoryRecursively, hos::Version_300),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetFileTimeStampRaw, hos::Version_300),
|
||||||
|
|
||||||
|
/* 4.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(QueryEntry, hos::Version_400),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../../fs/fs_common.hpp"
|
||||||
|
#include "../../fs/fs_query_range.hpp"
|
||||||
|
#include "../../fssystem/fssystem_utility.hpp"
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
class IStorage;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ams::fssrv::impl {
|
||||||
|
|
||||||
|
class StorageInterfaceAdapter final : public ams::sf::IServiceObject {
|
||||||
|
NON_COPYABLE(StorageInterfaceAdapter);
|
||||||
|
public:
|
||||||
|
enum class CommandId {
|
||||||
|
Read = 0,
|
||||||
|
Write = 1,
|
||||||
|
Flush = 2,
|
||||||
|
SetSize = 3,
|
||||||
|
GetSize = 4,
|
||||||
|
OperateRange = 5,
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
/* TODO: Nintendo uses fssystem::AsynchronousAccessStorage here. */
|
||||||
|
std::shared_ptr<fs::IStorage> base_storage;
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
|
||||||
|
os::ReadWriteLock invalidation_lock;
|
||||||
|
/* TODO: DataStorageContext. */
|
||||||
|
bool deep_retry_enabled = false;
|
||||||
|
public:
|
||||||
|
StorageInterfaceAdapter(fs::IStorage *storage);
|
||||||
|
StorageInterfaceAdapter(std::unique_ptr<fs::IStorage> storage);
|
||||||
|
explicit StorageInterfaceAdapter(std::shared_ptr<fs::IStorage> &&storage);
|
||||||
|
/* TODO: Other constructors. */
|
||||||
|
|
||||||
|
~StorageInterfaceAdapter();
|
||||||
|
private:
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> AcquireCacheInvalidationReadLock();
|
||||||
|
private:
|
||||||
|
/* Command API. */
|
||||||
|
Result Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size);
|
||||||
|
Result Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size);
|
||||||
|
Result Flush();
|
||||||
|
Result SetSize(s64 size);
|
||||||
|
Result GetSize(ams::sf::Out<s64> out);
|
||||||
|
Result OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size);
|
||||||
|
public:
|
||||||
|
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||||
|
/* 1.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(Read),
|
||||||
|
MAKE_SERVICE_COMMAND_META(Write),
|
||||||
|
MAKE_SERVICE_COMMAND_META(Flush),
|
||||||
|
MAKE_SERVICE_COMMAND_META(SetSize),
|
||||||
|
MAKE_SERVICE_COMMAND_META(GetSize),
|
||||||
|
|
||||||
|
/* 4.0.0- */
|
||||||
|
MAKE_SERVICE_COMMAND_META(OperateRange, hos::Version_400),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "fssystem/fssystem_utility.hpp"
|
||||||
|
#include "fssystem/fssystem_path_tool.hpp"
|
||||||
|
#include "fssystem/fssystem_subdirectory_filesystem.hpp"
|
||||||
|
#include "fssystem/fssystem_directory_redirection_filesystem.hpp"
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "fssystem_path_resolution_filesystem.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
class DirectoryRedirectionFileSystem : public IPathResolutionFileSystem<DirectoryRedirectionFileSystem> {
|
||||||
|
NON_COPYABLE(DirectoryRedirectionFileSystem);
|
||||||
|
private:
|
||||||
|
using PathResolutionFileSystem = IPathResolutionFileSystem<DirectoryRedirectionFileSystem>;
|
||||||
|
private:
|
||||||
|
char *before_dir;
|
||||||
|
size_t before_dir_len;
|
||||||
|
char *after_dir;
|
||||||
|
size_t after_dir_len;
|
||||||
|
bool unc_preserved;
|
||||||
|
public:
|
||||||
|
DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after);
|
||||||
|
DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc);
|
||||||
|
|
||||||
|
virtual ~DirectoryRedirectionFileSystem();
|
||||||
|
private:
|
||||||
|
Result GetNormalizedDirectoryPath(char **out, size_t *out_size, const char *dir);
|
||||||
|
Result Initialize(const char *before, const char *after);
|
||||||
|
public:
|
||||||
|
Result ResolveFullPath(char *out, size_t out_size, const char *relative_path);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../fs/fs_common.hpp"
|
||||||
|
#include "../fs/fsa/fs_ifile.hpp"
|
||||||
|
#include "../fs/fsa/fs_idirectory.hpp"
|
||||||
|
#include "../fs/fsa/fs_ifilesystem.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
template<typename Impl>
|
||||||
|
class IPathResolutionFileSystem : public fs::fsa::IFileSystem {
|
||||||
|
NON_COPYABLE(IPathResolutionFileSystem);
|
||||||
|
private:
|
||||||
|
std::shared_ptr<fs::fsa::IFileSystem> shared_fs;
|
||||||
|
fs::fsa::IFileSystem *base_fs;
|
||||||
|
bool unc_preserved;
|
||||||
|
public:
|
||||||
|
IPathResolutionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs) : shared_fs(std::move(fs)), unc_preserved(false) {
|
||||||
|
this->base_fs = this->shared_fs.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
IPathResolutionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, bool unc) : shared_fs(std::move(fs)), unc_preserved(unc) {
|
||||||
|
this->base_fs = this->shared_fs.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~IPathResolutionFileSystem() { /* ... */ }
|
||||||
|
protected:
|
||||||
|
constexpr inline bool IsUncPreserved() const {
|
||||||
|
return this->unc_preserved;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
virtual Result CreateFileImpl(const char *path, s64 size, int option) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->CreateFile(full_path, size, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result DeleteFileImpl(const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->DeleteFile(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result CreateDirectoryImpl(const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->CreateDirectory(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result DeleteDirectoryImpl(const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->DeleteDirectory(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result DeleteDirectoryRecursivelyImpl(const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result RenameFileImpl(const char *old_path, const char *new_path) override {
|
||||||
|
char old_full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
char new_full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path));
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path));
|
||||||
|
|
||||||
|
return this->base_fs->RenameFile(old_path, new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result RenameDirectoryImpl(const char *old_path, const char *new_path) override {
|
||||||
|
char old_full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
char new_full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(old_full_path, sizeof(old_full_path), old_path));
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(new_full_path, sizeof(new_full_path), new_path));
|
||||||
|
|
||||||
|
return this->base_fs->RenameDirectory(old_path, new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result GetEntryTypeImpl(fs::DirectoryEntryType *out, const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->GetEntryType(out, full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result OpenFileImpl(std::unique_ptr<fs::fsa::IFile> *out_file, const char *path, fs::OpenMode mode) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->OpenFile(out_file, full_path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result OpenDirectoryImpl(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const char *path, fs::OpenDirectoryMode mode) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result CommitImpl() override {
|
||||||
|
return this->base_fs->Rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result GetFreeSpaceSizeImpl(s64 *out, const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result GetTotalSpaceSizeImpl(s64 *out, const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result CleanDirectoryRecursivelyImpl(const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->CleanDirectoryRecursively(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result GetFileTimeStampRawImpl(fs::FileTimeStampRaw *out, const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result QueryEntryImpl(char *dst, size_t dst_size, const char *src, size_t src_size, fs::fsa::QueryId query, const char *path) override {
|
||||||
|
char full_path[fs::EntryNameLengthMax + 1];
|
||||||
|
R_TRY(static_cast<Impl*>(this)->ResolveFullPath(full_path, sizeof(full_path), path));
|
||||||
|
|
||||||
|
return this->base_fs->QueryEntry(dst, dst_size, src, src_size, query, full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* These aren't accessible as commands. */
|
||||||
|
virtual Result CommitProvisionallyImpl(s64 counter) override {
|
||||||
|
return this->base_fs->CommitProvisionally(counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result RollbackImpl() override {
|
||||||
|
return this->base_fs->Rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual Result FlushImpl() override {
|
||||||
|
return this->base_fs->Flush();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../fs/fs_common.hpp"
|
||||||
|
#include "../fs/fs_path_tool.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
namespace StringTraits = ::ams::fs::StringTraits;
|
||||||
|
|
||||||
|
using PathTool = ::ams::fs::PathTool;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "fssystem_path_resolution_filesystem.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
class SubDirectoryFileSystem : public IPathResolutionFileSystem<SubDirectoryFileSystem> {
|
||||||
|
NON_COPYABLE(SubDirectoryFileSystem);
|
||||||
|
private:
|
||||||
|
using PathResolutionFileSystem = IPathResolutionFileSystem<SubDirectoryFileSystem>;
|
||||||
|
private:
|
||||||
|
char *base_path;
|
||||||
|
size_t base_path_len;
|
||||||
|
bool unc_preserved;
|
||||||
|
public:
|
||||||
|
SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp);
|
||||||
|
SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc);
|
||||||
|
|
||||||
|
virtual ~SubDirectoryFileSystem();
|
||||||
|
private:
|
||||||
|
Result Initialize(const char *bp);
|
||||||
|
public:
|
||||||
|
Result ResolveFullPath(char *out, size_t out_size, const char *relative_path);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "../fs/fs_common.hpp"
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
class SemaphoreAdapter : public os::Semaphore {
|
||||||
|
public:
|
||||||
|
SemaphoreAdapter(int c, int mc) : os::Semaphore(c, mc) { /* ... */ }
|
||||||
|
|
||||||
|
bool try_lock() {
|
||||||
|
return this->TryAcquire();
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock() {
|
||||||
|
this->Release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "os/os_common_types.hpp"
|
#include "os/os_common_types.hpp"
|
||||||
|
#include "os/os_memory_common.hpp"
|
||||||
#include "os/os_managed_handle.hpp"
|
#include "os/os_managed_handle.hpp"
|
||||||
#include "os/os_process_handle.hpp"
|
#include "os/os_process_handle.hpp"
|
||||||
#include "os/os_mutex.hpp"
|
#include "os/os_mutex.hpp"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include <atmosphere/common.hpp>
|
||||||
|
|
||||||
|
namespace ams::os {
|
||||||
|
|
||||||
|
constexpr inline size_t MemoryPageSize = 0x1000;
|
||||||
|
|
||||||
|
constexpr inline size_t MemoryBlockUnitSize = 0x200000;
|
||||||
|
|
||||||
|
}
|
|
@ -15,34 +15,41 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "os_common_types.hpp"
|
#include "os_mutex.hpp"
|
||||||
|
#include "os_condvar.hpp"
|
||||||
|
|
||||||
namespace ams::os {
|
namespace ams::os {
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
class WaitableObjectList;
|
||||||
|
class WaitableHolderOfSemaphore;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class Semaphore {
|
class Semaphore {
|
||||||
|
friend class impl::WaitableHolderOfSemaphore;
|
||||||
NON_COPYABLE(Semaphore);
|
NON_COPYABLE(Semaphore);
|
||||||
NON_MOVEABLE(Semaphore);
|
NON_MOVEABLE(Semaphore);
|
||||||
private:
|
private:
|
||||||
::Semaphore s;
|
util::TypedStorage<impl::WaitableObjectList, sizeof(util::IntrusiveListNode), alignof(util::IntrusiveListNode)> waitlist;
|
||||||
|
os::Mutex mutex;
|
||||||
|
os::ConditionVariable condvar;
|
||||||
|
int count;
|
||||||
|
int max_count;
|
||||||
public:
|
public:
|
||||||
Semaphore() {
|
explicit Semaphore(int c, int mc);
|
||||||
semaphoreInit(&s, 0);
|
~Semaphore();
|
||||||
}
|
|
||||||
|
|
||||||
Semaphore(u64 c) {
|
void Acquire();
|
||||||
semaphoreInit(&s, c);
|
bool TryAcquire();
|
||||||
}
|
bool TimedAcquire(u64 timeout);
|
||||||
|
|
||||||
void Signal() {
|
void Release();
|
||||||
semaphoreSignal(&s);
|
void Release(int count);
|
||||||
}
|
|
||||||
|
|
||||||
void Wait() {
|
constexpr inline int GetCurrentCount() const {
|
||||||
semaphoreWait(&s);
|
return this->count;
|
||||||
}
|
|
||||||
|
|
||||||
bool TryWait() {
|
|
||||||
return semaphoreTryWait(&s);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "os_common_types.hpp"
|
#include "os_common_types.hpp"
|
||||||
|
#include "os_memory_common.hpp"
|
||||||
|
|
||||||
namespace ams::os {
|
namespace ams::os {
|
||||||
|
|
||||||
|
@ -62,9 +63,9 @@ namespace ams::os {
|
||||||
class StaticThread {
|
class StaticThread {
|
||||||
NON_COPYABLE(StaticThread);
|
NON_COPYABLE(StaticThread);
|
||||||
NON_MOVEABLE(StaticThread);
|
NON_MOVEABLE(StaticThread);
|
||||||
static_assert(util::IsAligned(StackSize, 0x1000), "StaticThread must have aligned resource size");
|
static_assert(util::IsAligned(StackSize, os::MemoryPageSize), "StaticThread must have aligned resource size");
|
||||||
private:
|
private:
|
||||||
alignas(0x1000) u8 stack_mem[StackSize];
|
alignas(os::MemoryPageSize) u8 stack_mem[StackSize];
|
||||||
::Thread thr;
|
::Thread thr;
|
||||||
public:
|
public:
|
||||||
constexpr StaticThread() : stack_mem{}, thr{} { /* ... */ }
|
constexpr StaticThread() : stack_mem{}, thr{} { /* ... */ }
|
||||||
|
|
|
@ -25,6 +25,7 @@ namespace ams::os {
|
||||||
class InterruptEvent;
|
class InterruptEvent;
|
||||||
class Thread;
|
class Thread;
|
||||||
class MessageQueue;
|
class MessageQueue;
|
||||||
|
class Semaphore;
|
||||||
|
|
||||||
namespace impl {
|
namespace impl {
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ namespace ams::os {
|
||||||
WaitableHolder(SystemEvent *event);
|
WaitableHolder(SystemEvent *event);
|
||||||
WaitableHolder(InterruptEvent *event);
|
WaitableHolder(InterruptEvent *event);
|
||||||
WaitableHolder(Thread *thread);
|
WaitableHolder(Thread *thread);
|
||||||
|
WaitableHolder(Semaphore *semaphore);
|
||||||
WaitableHolder(MessageQueue *message_queue, MessageQueueWaitKind wait_kind);
|
WaitableHolder(MessageQueue *message_queue, MessageQueueWaitKind wait_kind);
|
||||||
|
|
||||||
~WaitableHolder();
|
~WaitableHolder();
|
||||||
|
|
|
@ -74,7 +74,7 @@ namespace ams::emummc {
|
||||||
/* Retrieve and cache values. */
|
/* Retrieve and cache values. */
|
||||||
{
|
{
|
||||||
|
|
||||||
typename std::aligned_storage<2 * (MaxDirLen + 1), 0x1000>::type path_storage;
|
typename std::aligned_storage<2 * (MaxDirLen + 1), os::MemoryPageSize>::type path_storage;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
char file_path[MaxDirLen + 1];
|
char file_path[MaxDirLen + 1];
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace ams::dd {
|
||||||
|
|
||||||
uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size) {
|
uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size) {
|
||||||
u64 virtual_addr;
|
u64 virtual_addr;
|
||||||
const u64 aligned_addr = util::AlignDown(phys_addr, 0x1000);
|
const u64 aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize);
|
||||||
const size_t offset = phys_addr - aligned_addr;
|
const size_t offset = phys_addr - aligned_addr;
|
||||||
const u64 aligned_size = size + offset;
|
const u64 aligned_size = size + offset;
|
||||||
R_TRY_CATCH(svcQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) {
|
R_TRY_CATCH(svcQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) {
|
||||||
|
|
248
stratosphere/libstratosphere/source/fs/fs_path_tool.cpp
Normal file
248
stratosphere/libstratosphere/source/fs/fs_path_tool.cpp
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
|
||||||
|
Result PathTool::Normalize(char *out, size_t *out_len, const char *src, size_t max_out_size, bool unc_preserved) {
|
||||||
|
/* Paths must start with / */
|
||||||
|
R_UNLESS(IsSeparator(src[0]), fs::ResultInvalidPathFormat());
|
||||||
|
|
||||||
|
bool skip_next_sep = false;
|
||||||
|
size_t i = 0;
|
||||||
|
size_t len = 0;
|
||||||
|
|
||||||
|
while (!IsNullTerminator(src[i])) {
|
||||||
|
if (IsSeparator(src[i])) {
|
||||||
|
/* Swallow separators. */
|
||||||
|
while (IsSeparator(src[++i])) { }
|
||||||
|
if (IsNullTerminator(src[i])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle skip if needed */
|
||||||
|
if (!skip_next_sep) {
|
||||||
|
if (len + 1 == max_out_size) {
|
||||||
|
out[len] = StringTraits::NullTerminator;
|
||||||
|
if (out_len != nullptr) {
|
||||||
|
*out_len = len;
|
||||||
|
}
|
||||||
|
return fs::ResultTooLongPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
out[len++] = StringTraits::DirectorySeparator;
|
||||||
|
|
||||||
|
if (unc_preserved && len == 1) {
|
||||||
|
while (len < i) {
|
||||||
|
if (len + 1 == max_out_size) {
|
||||||
|
out[len] = StringTraits::NullTerminator;
|
||||||
|
if (out_len != nullptr) {
|
||||||
|
*out_len = len;
|
||||||
|
}
|
||||||
|
return fs::ResultTooLongPath();
|
||||||
|
}
|
||||||
|
out[len++] = StringTraits::DirectorySeparator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
skip_next_sep = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See length of current dir. */
|
||||||
|
size_t dir_len = 0;
|
||||||
|
while (!IsSeparator(src[i+dir_len]) && !IsNullTerminator(src[i+dir_len])) {
|
||||||
|
dir_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsCurrentDirectory(&src[i])) {
|
||||||
|
skip_next_sep = true;
|
||||||
|
} else if (IsParentDirectory(&src[i])) {
|
||||||
|
AMS_ASSERT(IsSeparator(out[0]));
|
||||||
|
AMS_ASSERT(IsSeparator(out[len - 1]));
|
||||||
|
R_UNLESS(len != 1, fs::ResultDirectoryUnobtainable());
|
||||||
|
|
||||||
|
/* Walk up a directory. */
|
||||||
|
len -= 2;
|
||||||
|
while (!IsSeparator(out[len])) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Copy, possibly truncating. */
|
||||||
|
if (len + dir_len + 1 <= max_out_size) {
|
||||||
|
for (size_t j = 0; j < dir_len; j++) {
|
||||||
|
out[len++] = src[i+j];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const size_t copy_len = max_out_size - 1 - len;
|
||||||
|
for (size_t j = 0; j < copy_len; j++) {
|
||||||
|
out[len++] = src[i+j];
|
||||||
|
}
|
||||||
|
out[len] = StringTraits::NullTerminator;
|
||||||
|
if (out_len != nullptr) {
|
||||||
|
*out_len = len;
|
||||||
|
}
|
||||||
|
return fs::ResultTooLongPath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i += dir_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skip_next_sep) {
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == 0 && max_out_size) {
|
||||||
|
out[len++] = StringTraits::DirectorySeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
R_UNLESS(max_out_size >= len - 1, fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Null terminate. */
|
||||||
|
out[len] = StringTraits::NullTerminator;
|
||||||
|
if (out_len != nullptr) {
|
||||||
|
*out_len = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Assert normalized. */
|
||||||
|
bool normalized = false;
|
||||||
|
AMS_ASSERT(unc_preserved || (R_SUCCEEDED(IsNormalized(&normalized, out)) && normalized));
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result PathTool::IsNormalized(bool *out, const char *path) {
|
||||||
|
/* Nintendo uses a state machine here. */
|
||||||
|
enum class PathState {
|
||||||
|
Start,
|
||||||
|
Normal,
|
||||||
|
FirstSeparator,
|
||||||
|
Separator,
|
||||||
|
CurrentDir,
|
||||||
|
ParentDir,
|
||||||
|
WindowsDriveLetter,
|
||||||
|
};
|
||||||
|
|
||||||
|
PathState state = PathState::Start;
|
||||||
|
|
||||||
|
for (const char *cur = path; *cur != StringTraits::NullTerminator; cur++) {
|
||||||
|
const char c = *cur;
|
||||||
|
switch (state) {
|
||||||
|
case PathState::Start:
|
||||||
|
if (IsWindowsDriveCharacter(c)) {
|
||||||
|
state = PathState::WindowsDriveLetter;
|
||||||
|
} else if (IsSeparator(c)) {
|
||||||
|
state = PathState::FirstSeparator;
|
||||||
|
} else {
|
||||||
|
return fs::ResultInvalidPathFormat();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathState::Normal:
|
||||||
|
if (IsSeparator(c)) {
|
||||||
|
state = PathState::Separator;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathState::FirstSeparator:
|
||||||
|
case PathState::Separator:
|
||||||
|
if (IsSeparator(c)) {
|
||||||
|
*out = false;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else if (IsDot(c)) {
|
||||||
|
state = PathState::CurrentDir;
|
||||||
|
} else {
|
||||||
|
state = PathState::Normal;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathState::CurrentDir:
|
||||||
|
if (IsSeparator(c)) {
|
||||||
|
*out = false;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else if (IsDot(c)) {
|
||||||
|
state = PathState::ParentDir;
|
||||||
|
} else {
|
||||||
|
state = PathState::Normal;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathState::ParentDir:
|
||||||
|
if (IsSeparator(c)) {
|
||||||
|
*out = false;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else {
|
||||||
|
state = PathState::Normal;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PathState::WindowsDriveLetter:
|
||||||
|
if (IsDriveSeparator(c)) {
|
||||||
|
*out = true;
|
||||||
|
return ResultSuccess();
|
||||||
|
} else {
|
||||||
|
return fs::ResultInvalidPathFormat();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case PathState::Start:
|
||||||
|
case PathState::WindowsDriveLetter:
|
||||||
|
return fs::ResultInvalidPathFormat();
|
||||||
|
case PathState::FirstSeparator:
|
||||||
|
case PathState::Normal:
|
||||||
|
*out = true;
|
||||||
|
break;
|
||||||
|
case PathState::CurrentDir:
|
||||||
|
case PathState::ParentDir:
|
||||||
|
case PathState::Separator:
|
||||||
|
*out = false;
|
||||||
|
break;
|
||||||
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PathTool::IsSubPath(const char *lhs, const char *rhs) {
|
||||||
|
AMS_ASSERT(lhs != nullptr);
|
||||||
|
AMS_ASSERT(rhs != nullptr);
|
||||||
|
|
||||||
|
/* Special case certain paths. */
|
||||||
|
if (IsSeparator(lhs[0]) && !IsSeparator(lhs[1]) && IsSeparator(rhs[0]) && IsSeparator(rhs[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (IsSeparator(rhs[0]) && !IsSeparator(rhs[1]) && IsSeparator(lhs[0]) && IsSeparator(lhs[1])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (IsSeparator(lhs[0]) && IsNullTerminator(lhs[1]) && IsSeparator(rhs[0]) && !IsNullTerminator(rhs[1])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (IsSeparator(rhs[0]) && IsNullTerminator(rhs[1]) && IsSeparator(lhs[0]) && !IsNullTerminator(lhs[1])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check subpath. */
|
||||||
|
for (size_t i = 0; /* No terminating condition */; i++) {
|
||||||
|
if (IsNullTerminator(lhs[i])) {
|
||||||
|
return IsSeparator(rhs[i]);
|
||||||
|
} else if (IsNullTerminator(rhs[i])) {
|
||||||
|
return IsSeparator(lhs[i]);
|
||||||
|
} else if (lhs[i] != rhs[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
47
stratosphere/libstratosphere/source/fs/fs_path_utils.cpp
Normal file
47
stratosphere/libstratosphere/source/fs/fs_path_utils.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace ams::fs {
|
||||||
|
|
||||||
|
Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
|
||||||
|
const char *cur = path;
|
||||||
|
size_t name_len = 0;
|
||||||
|
|
||||||
|
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
|
||||||
|
const char c = *(cur++);
|
||||||
|
|
||||||
|
/* If terminated, we're done. */
|
||||||
|
R_UNLESS(c != StringTraits::NullTerminator, ResultSuccess());
|
||||||
|
|
||||||
|
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
||||||
|
/* We should do this. */
|
||||||
|
|
||||||
|
/* Banned characters: :*?<>| */
|
||||||
|
R_UNLESS((c != ':' && c != '*' && c != '?' && c != '<' && c != '>' && c != '|'), fs::ResultInvalidCharacter());
|
||||||
|
|
||||||
|
name_len++;
|
||||||
|
/* Check for separator. */
|
||||||
|
if (c == '\\' || c == '/') {
|
||||||
|
name_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs::ResultTooLongPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,343 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
|
||||||
|
|
||||||
|
namespace ams::fssrv::impl {
|
||||||
|
|
||||||
|
FileInterfaceAdapter::FileInterfaceAdapter(std::unique_ptr<fs::fsa::IFile> &&file, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema)
|
||||||
|
: parent_filesystem(std::move(parent)), base_file(std::move(file)), open_count_semaphore(std::move(sema))
|
||||||
|
{
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
FileInterfaceAdapter::~FileInterfaceAdapter() {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileInterfaceAdapter::InvalidateCache() {
|
||||||
|
AMS_ASSERT(this->parent_filesystem->IsDeepRetryEnabled());
|
||||||
|
std::scoped_lock<os::ReadWriteLock> scoped_write_lock(this->parent_filesystem->GetReadWriteLockForCacheInvalidation());
|
||||||
|
this->base_file->OperateRange(nullptr, 0, fs::OperationId::InvalidateCache, 0, std::numeric_limits<s64>::max(), nullptr, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::Read(ams::sf::Out<s64> out, s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size, fs::ReadOption option) {
|
||||||
|
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
||||||
|
/* TODO: Deep retry */
|
||||||
|
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
||||||
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
|
||||||
|
size_t read_size = 0;
|
||||||
|
R_TRY(this->base_file->Read(&read_size, offset, buffer.GetPointer(), static_cast<size_t>(size), option));
|
||||||
|
|
||||||
|
out.SetValue(read_size);
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size, fs::WriteOption option) {
|
||||||
|
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
|
||||||
|
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
||||||
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
return this->base_file->Write(offset, buffer.GetPointer(), size, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::Flush() {
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
return this->base_file->Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::SetSize(s64 size) {
|
||||||
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
return this->base_file->SetSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::GetSize(ams::sf::Out<s64> out) {
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
return this->base_file->GetSize(out.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileInterfaceAdapter::OperateRange(ams::sf::Out<fs::FileQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
|
||||||
|
/* N includes this redundant check, so we will too. */
|
||||||
|
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
|
||||||
|
|
||||||
|
out->Clear();
|
||||||
|
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
fs::FileQueryRangeInfo info;
|
||||||
|
R_TRY(this->base_file->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
|
||||||
|
out->Merge(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryInterfaceAdapter::DirectoryInterfaceAdapter(std::unique_ptr<fs::fsa::IDirectory> &&dir, std::shared_ptr<FileSystemInterfaceAdapter> &&parent, std::unique_lock<fssystem::SemaphoreAdapter> &&sema)
|
||||||
|
: parent_filesystem(std::move(parent)), base_dir(std::move(dir)), open_count_semaphore(std::move(sema))
|
||||||
|
{
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryInterfaceAdapter::~DirectoryInterfaceAdapter() {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DirectoryInterfaceAdapter::Read(ams::sf::Out<s64> out, const ams::sf::OutBuffer &out_entries) {
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
const size_t max_num_entries = out_entries.GetSize() / sizeof(fs::DirectoryEntry);
|
||||||
|
R_UNLESS(max_num_entries >= 0, fs::ResultInvalidSize());
|
||||||
|
|
||||||
|
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
||||||
|
return this->base_dir->Read(out.GetPointer(), reinterpret_cast<fs::DirectoryEntry *>(out_entries.GetPointer()), max_num_entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DirectoryInterfaceAdapter::GetEntryCount(ams::sf::Out<s64> out) {
|
||||||
|
auto read_lock = this->parent_filesystem->AcquireCacheInvalidationReadLock();
|
||||||
|
return this->base_dir->GetEntryCount(out.GetPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemInterfaceAdapter::FileSystemInterfaceAdapter(std::shared_ptr<fs::fsa::IFileSystem> &&fs, bool open_limited)
|
||||||
|
: base_fs(std::move(fs)), open_count_limited(open_limited), deep_retry_enabled(false)
|
||||||
|
{
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemInterfaceAdapter::~FileSystemInterfaceAdapter() {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSystemInterfaceAdapter::IsDeepRetryEnabled() const {
|
||||||
|
return this->deep_retry_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileSystemInterfaceAdapter::IsAccessFailureDetectionObserved() const {
|
||||||
|
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
|
||||||
|
AMS_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> FileSystemInterfaceAdapter::AcquireCacheInvalidationReadLock() {
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> lock;
|
||||||
|
if (this->deep_retry_enabled) {
|
||||||
|
lock.emplace(this->invalidation_lock);
|
||||||
|
}
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
os::ReadWriteLock &FileSystemInterfaceAdapter::GetReadWriteLockForCacheInvalidation() {
|
||||||
|
return this->invalidation_lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::CreateFile(const fssrv::sf::Path &path, s64 size, s32 option) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->CreateFile(normalizer.GetPath(), size, option);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::DeleteFile(const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->DeleteFile(normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::CreateDirectory(const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultPathAlreadyExists());
|
||||||
|
|
||||||
|
return this->base_fs->CreateDirectory(normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::DeleteDirectory(const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultDirectoryNotDeletable());
|
||||||
|
|
||||||
|
return this->base_fs->DeleteDirectory(normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::DeleteDirectoryRecursively(const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
R_UNLESS(strncmp(normalizer.GetPath(), "/", 2) != 0, fs::ResultDirectoryNotDeletable());
|
||||||
|
|
||||||
|
return this->base_fs->DeleteDirectoryRecursively(normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::RenameFile(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer old_normalizer(old_path.str);
|
||||||
|
PathNormalizer new_normalizer(new_path.str);
|
||||||
|
R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult());
|
||||||
|
R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::RenameDirectory(const fssrv::sf::Path &old_path, const fssrv::sf::Path &new_path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer old_normalizer(old_path.str);
|
||||||
|
PathNormalizer new_normalizer(new_path.str);
|
||||||
|
R_UNLESS(old_normalizer.GetPath() != nullptr, old_normalizer.GetResult());
|
||||||
|
R_UNLESS(new_normalizer.GetPath() != nullptr, new_normalizer.GetResult());
|
||||||
|
|
||||||
|
const bool is_subpath = fssystem::PathTool::IsSubPath(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||||
|
R_UNLESS(!is_subpath, fs::ResultDirectoryNotRenamable());
|
||||||
|
|
||||||
|
return this->base_fs->RenameFile(old_normalizer.GetPath(), new_normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::GetEntryType(ams::sf::Out<u32> out, const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
static_assert(sizeof(*out.GetPointer()) == sizeof(fs::DirectoryEntryType));
|
||||||
|
return this->base_fs->GetEntryType(reinterpret_cast<fs::DirectoryEntryType *>(out.GetPointer()), normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::OpenFile(ams::sf::Out<std::shared_ptr<FileInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
|
||||||
|
if (this->open_count_limited) {
|
||||||
|
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
|
||||||
|
AMS_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
||||||
|
std::unique_ptr<fs::fsa::IFile> file;
|
||||||
|
R_TRY(this->base_fs->OpenFile(&file, normalizer.GetPath(), static_cast<fs::OpenMode>(mode)));
|
||||||
|
|
||||||
|
/* TODO: This is a hack to get the mitm API to work. Better solution? */
|
||||||
|
const auto target_object_id = file->GetDomainObjectId();
|
||||||
|
|
||||||
|
/* TODO: N creates an nn::fssystem::AsynchronousAccessFile here. */
|
||||||
|
|
||||||
|
std::shared_ptr<FileSystemInterfaceAdapter> shared_this = this->shared_from_this();
|
||||||
|
std::shared_ptr<FileInterfaceAdapter> file_intf = std::make_shared<FileInterfaceAdapter>(std::move(file), std::move(shared_this), std::move(open_count_semaphore));
|
||||||
|
R_UNLESS(file_intf != nullptr, fs::ResultAllocationFailureInFileSystemInterfaceAdapter());
|
||||||
|
|
||||||
|
out.SetValue(std::move(file_intf), target_object_id);
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::OpenDirectory(ams::sf::Out<std::shared_ptr<DirectoryInterfaceAdapter>> out, const fssrv::sf::Path &path, u32 mode) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
std::unique_lock<fssystem::SemaphoreAdapter> open_count_semaphore;
|
||||||
|
if (this->open_count_limited) {
|
||||||
|
/* TODO: This calls into fssrv::FileSystemProxyImpl, which we don't have yet. */
|
||||||
|
AMS_ASSERT(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
||||||
|
std::unique_ptr<fs::fsa::IDirectory> dir;
|
||||||
|
R_TRY(this->base_fs->OpenDirectory(&dir, normalizer.GetPath(), static_cast<fs::OpenDirectoryMode>(mode)));
|
||||||
|
|
||||||
|
/* TODO: This is a hack to get the mitm API to work. Better solution? */
|
||||||
|
const auto target_object_id = dir->GetDomainObjectId();
|
||||||
|
|
||||||
|
std::shared_ptr<FileSystemInterfaceAdapter> shared_this = this->shared_from_this();
|
||||||
|
std::shared_ptr<DirectoryInterfaceAdapter> dir_intf = std::make_shared<DirectoryInterfaceAdapter>(std::move(dir), std::move(shared_this), std::move(open_count_semaphore));
|
||||||
|
R_UNLESS(dir_intf != nullptr, fs::ResultAllocationFailureInFileSystemInterfaceAdapter());
|
||||||
|
|
||||||
|
out.SetValue(std::move(dir_intf), target_object_id);
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::Commit() {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
return this->base_fs->Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::GetFreeSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->GetFreeSpaceSize(out.GetPointer(), normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::GetTotalSpaceSize(ams::sf::Out<s64> out, const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->GetTotalSpaceSize(out.GetPointer(), normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::CleanDirectoryRecursively(const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->CleanDirectoryRecursively(normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::GetFileTimeStampRaw(ams::sf::Out<fs::FileTimeStampRaw> out, const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
PathNormalizer normalizer(path.str);
|
||||||
|
R_UNLESS(normalizer.GetPath() != nullptr, normalizer.GetResult());
|
||||||
|
|
||||||
|
return this->base_fs->GetFileTimeStampRaw(out.GetPointer(), normalizer.GetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
Result FileSystemInterfaceAdapter::QueryEntry(const ams::sf::OutBuffer &out_buf, const ams::sf::InBuffer &in_buf, s32 query_id, const fssrv::sf::Path &path) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
|
/* TODO: Nintendo does not normalize the path. Should we? */
|
||||||
|
|
||||||
|
char *dst = reinterpret_cast< char *>(out_buf.GetPointer());
|
||||||
|
const char *src = reinterpret_cast<const char *>(in_buf.GetPointer());
|
||||||
|
return this->base_fs->QueryEntry(dst, out_buf.GetSize(), src, in_buf.GetSize(), static_cast<fs::fsa::QueryId>(query_id), path.str);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace ams::fssrv {
|
||||||
|
|
||||||
|
Result PathNormalizer::Normalize(const char **out_path, Buffer *out_buf, const char *path, bool preserve_unc, bool preserve_tail_sep, bool has_mount_name) {
|
||||||
|
/* Clear output. */
|
||||||
|
*out_path = nullptr;
|
||||||
|
*out_buf = Buffer();
|
||||||
|
|
||||||
|
/* Find start of path. */
|
||||||
|
const char *path_start = path;
|
||||||
|
if (has_mount_name) {
|
||||||
|
while (path_start < path + fs::MountNameLengthMax + 1) {
|
||||||
|
if (fssystem::PathTool::IsNullTerminator(*path_start)) {
|
||||||
|
break;
|
||||||
|
} else if (fssystem::PathTool::IsDriveSeparator(*(path_start++))) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R_UNLESS(path < path_start - 1, fs::ResultInvalidPath());
|
||||||
|
R_UNLESS(fssystem::PathTool::IsDriveSeparator(*(path_start - 1)), fs::ResultInvalidPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if we're normalized. */
|
||||||
|
bool normalized = false;
|
||||||
|
R_TRY(fssystem::PathTool::IsNormalized(&normalized, path_start));
|
||||||
|
|
||||||
|
if (normalized) {
|
||||||
|
/* If we're already normalized, no allocation is needed. */
|
||||||
|
*out_path = path;
|
||||||
|
} else {
|
||||||
|
/* Allocate a new buffer. */
|
||||||
|
auto buffer = std::make_unique<char[]>(fs::EntryNameLengthMax + 1);
|
||||||
|
R_UNLESS(buffer != nullptr, fs::ResultAllocationFailureInPathNormalizer());
|
||||||
|
|
||||||
|
/* Copy in mount name. */
|
||||||
|
const size_t mount_name_len = path_start - path;
|
||||||
|
std::memcpy(buffer.get(), path, mount_name_len);
|
||||||
|
|
||||||
|
/* Generate normalized path. */
|
||||||
|
size_t normalized_len = 0;
|
||||||
|
R_TRY(fssystem::PathTool::Normalize(buffer.get() + mount_name_len, &normalized_len, path_start, fs::EntryNameLengthMax + 1 - mount_name_len, preserve_unc));
|
||||||
|
|
||||||
|
/* Preserve the tail separator, if we should. */
|
||||||
|
if (preserve_tail_sep) {
|
||||||
|
if (fssystem::PathTool::IsSeparator(path[strnlen(path, fs::EntryNameLengthMax) - 1])) {
|
||||||
|
/* Nintendo doesn't actually validate this. */
|
||||||
|
R_UNLESS(mount_name_len + normalized_len < fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||||
|
buffer[mount_name_len + normalized_len] = fssystem::StringTraits::DirectorySeparator;
|
||||||
|
buffer[mount_name_len + normalized_len + 1] = fssystem::StringTraits::NullTerminator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save output. */
|
||||||
|
*out_path = buffer.get();
|
||||||
|
*out_buf = std::move(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,45 +13,76 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include "fsmitm_istorage_interface.hpp"
|
#include <stratosphere.hpp>
|
||||||
|
#include <stratosphere/fssrv/fssrv_interface_adapters.hpp>
|
||||||
|
|
||||||
namespace ams::mitm::fs {
|
namespace ams::fssrv::impl {
|
||||||
|
|
||||||
using namespace ams::fs;
|
StorageInterfaceAdapter::StorageInterfaceAdapter(fs::IStorage *storage) : base_storage(storage) {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
Result IStorageInterface::Read(s64 offset, const sf::OutNonSecureBuffer &buffer, s64 size) {
|
StorageInterfaceAdapter::StorageInterfaceAdapter(std::unique_ptr<fs::IStorage> storage) : base_storage(storage.release()) {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageInterfaceAdapter::StorageInterfaceAdapter(std::shared_ptr<fs::IStorage> &&storage) : base_storage(std::move(storage)) {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageInterfaceAdapter::~StorageInterfaceAdapter() {
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> StorageInterfaceAdapter::AcquireCacheInvalidationReadLock() {
|
||||||
|
std::optional<std::shared_lock<os::ReadWriteLock>> lock;
|
||||||
|
if (this->deep_retry_enabled) {
|
||||||
|
lock.emplace(this->invalidation_lock);
|
||||||
|
}
|
||||||
|
return lock;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result StorageInterfaceAdapter::Read(s64 offset, const ams::sf::OutNonSecureBuffer &buffer, s64 size) {
|
||||||
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
/* TODO: N retries on ResultDataCorrupted, we may want to eventually. */
|
||||||
|
/* TODO: Deep retry */
|
||||||
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
||||||
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
return this->base_storage->Read(offset, buffer.GetPointer(), size);
|
return this->base_storage->Read(offset, buffer.GetPointer(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IStorageInterface::Write(s64 offset, const sf::InNonSecureBuffer &buffer, s64 size) {
|
Result StorageInterfaceAdapter::Write(s64 offset, const ams::sf::InNonSecureBuffer &buffer, s64 size) {
|
||||||
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
|
/* TODO: N increases thread priority temporarily when writing. We may want to eventually. */
|
||||||
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
|
||||||
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
return this->base_storage->Write(offset, buffer.GetPointer(), size);
|
return this->base_storage->Write(offset, buffer.GetPointer(), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IStorageInterface::Flush() {
|
Result StorageInterfaceAdapter::Flush() {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
return this->base_storage->Flush();
|
return this->base_storage->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IStorageInterface::SetSize(s64 size) {
|
Result StorageInterfaceAdapter::SetSize(s64 size) {
|
||||||
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
R_UNLESS(size >= 0, fs::ResultInvalidSize());
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
return this->base_storage->SetSize(size);
|
return this->base_storage->SetSize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IStorageInterface::GetSize(sf::Out<s64> out) {
|
Result StorageInterfaceAdapter::GetSize(ams::sf::Out<s64> out) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
return this->base_storage->GetSize(out.GetPointer());
|
return this->base_storage->GetSize(out.GetPointer());
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IStorageInterface::OperateRange(sf::Out<StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
|
Result StorageInterfaceAdapter::OperateRange(ams::sf::Out<fs::StorageQueryRangeInfo> out, s32 op_id, s64 offset, s64 size) {
|
||||||
/* N includes this redundant check, so we will too. */
|
/* N includes this redundant check, so we will too. */
|
||||||
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
|
R_UNLESS(out.GetPointer() != nullptr, fs::ResultNullptrArgument());
|
||||||
|
|
||||||
out->Clear();
|
out->Clear();
|
||||||
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
|
if (op_id == static_cast<s32>(fs::OperationId::QueryRange)) {
|
||||||
|
auto read_lock = this->AcquireCacheInvalidationReadLock();
|
||||||
|
|
||||||
fs::StorageQueryRangeInfo info;
|
fs::StorageQueryRangeInfo info;
|
||||||
R_TRY(this->base_storage->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
|
R_TRY(this->base_storage->OperateRange(&info, sizeof(info), fs::OperationId::QueryRange, offset, size, nullptr, 0));
|
||||||
out->Merge(info);
|
out->Merge(info);
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after) : PathResolutionFileSystem(fs) {
|
||||||
|
this->before_dir = nullptr;
|
||||||
|
this->after_dir = nullptr;
|
||||||
|
R_ASSERT(this->Initialize(before, after));
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryRedirectionFileSystem::DirectoryRedirectionFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *before, const char *after, bool unc) : PathResolutionFileSystem(fs, unc) {
|
||||||
|
this->before_dir = nullptr;
|
||||||
|
this->after_dir = nullptr;
|
||||||
|
R_ASSERT(this->Initialize(before, after));
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryRedirectionFileSystem::~DirectoryRedirectionFileSystem() {
|
||||||
|
if (this->before_dir != nullptr) {
|
||||||
|
std::free(this->before_dir);
|
||||||
|
}
|
||||||
|
if (this->after_dir != nullptr) {
|
||||||
|
std::free(this->after_dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DirectoryRedirectionFileSystem::GetNormalizedDirectoryPath(char **out, size_t *out_size, const char *dir) {
|
||||||
|
/* Clear output. */
|
||||||
|
*out = nullptr;
|
||||||
|
*out_size = 0;
|
||||||
|
|
||||||
|
/* Make sure the path isn't too long. */
|
||||||
|
R_UNLESS(strnlen(dir, fs::EntryNameLengthMax + 1) <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Normalize the path. */
|
||||||
|
char normalized_path[fs::EntryNameLengthMax + 2];
|
||||||
|
size_t normalized_path_len;
|
||||||
|
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, dir, sizeof(normalized_path), this->IsUncPreserved()));
|
||||||
|
|
||||||
|
/* Ensure terminating '/' */
|
||||||
|
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||||
|
AMS_ASSERT(normalized_path_len + 2 <= sizeof(normalized_path));
|
||||||
|
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
|
||||||
|
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
|
||||||
|
|
||||||
|
normalized_path_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate new path. */
|
||||||
|
const size_t size = normalized_path_len + 1;
|
||||||
|
char *new_dir = static_cast<char *>(std::malloc(size));
|
||||||
|
AMS_ASSERT(new_dir != nullptr);
|
||||||
|
/* TODO: custom ResultAllocationFailure? */
|
||||||
|
|
||||||
|
/* Copy path in. */
|
||||||
|
std::memcpy(new_dir, normalized_path, normalized_path_len);
|
||||||
|
new_dir[normalized_path_len] = StringTraits::NullTerminator;
|
||||||
|
|
||||||
|
/* Set output. */
|
||||||
|
*out = new_dir;
|
||||||
|
*out_size = size;
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) {
|
||||||
|
/* Normalize both directories. */
|
||||||
|
this->GetNormalizedDirectoryPath(&this->before_dir, &this->before_dir_len, before);
|
||||||
|
this->GetNormalizedDirectoryPath(&this->after_dir, &this->after_dir_len, after);
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DirectoryRedirectionFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||||
|
/* Normalize the relative path. */
|
||||||
|
char normalized_rel_path[fs::EntryNameLengthMax + 1];
|
||||||
|
size_t normalized_rel_path_len;
|
||||||
|
R_TRY(PathTool::Normalize(normalized_rel_path, &normalized_rel_path_len, relative_path, sizeof(normalized_rel_path), this->IsUncPreserved()));
|
||||||
|
|
||||||
|
const bool is_prefixed = std::memcmp(normalized_rel_path, this->before_dir, this->before_dir_len - 2) == 0 &&
|
||||||
|
(PathTool::IsSeparator(normalized_rel_path[this->before_dir_len - 2]) || PathTool::IsNullTerminator(normalized_rel_path[this->before_dir_len - 2]));
|
||||||
|
if (is_prefixed) {
|
||||||
|
const size_t before_prefix_len = this->before_dir_len - 2;
|
||||||
|
const size_t after_prefix_len = this->after_dir_len - 2;
|
||||||
|
const size_t final_str_len = after_prefix_len + normalized_rel_path_len - before_prefix_len;
|
||||||
|
R_UNLESS(final_str_len < out_size, fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Copy normalized path. */
|
||||||
|
std::memcpy(out, this->after_dir, after_prefix_len);
|
||||||
|
std::memcpy(out + after_prefix_len, normalized_rel_path + before_prefix_len, normalized_rel_path_len - before_prefix_len);
|
||||||
|
out[final_str_len] = StringTraits::NullTerminator;
|
||||||
|
} else {
|
||||||
|
/* Path is not prefixed. */
|
||||||
|
R_UNLESS(normalized_rel_path_len + 1 <= out_size, fs::ResultTooLongPath());
|
||||||
|
std::memcpy(out, normalized_rel_path, normalized_rel_path_len);
|
||||||
|
out[normalized_rel_path_len] = StringTraits::NullTerminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include <stratosphere.hpp>
|
||||||
|
|
||||||
|
namespace ams::fssystem {
|
||||||
|
|
||||||
|
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp) : PathResolutionFileSystem(fs) {
|
||||||
|
this->base_path = nullptr;
|
||||||
|
R_ASSERT(this->Initialize(bp));
|
||||||
|
}
|
||||||
|
|
||||||
|
SubDirectoryFileSystem::SubDirectoryFileSystem(std::shared_ptr<fs::fsa::IFileSystem> fs, const char *bp, bool unc) : PathResolutionFileSystem(fs, unc) {
|
||||||
|
this->base_path = nullptr;
|
||||||
|
R_ASSERT(this->Initialize(bp));
|
||||||
|
}
|
||||||
|
|
||||||
|
SubDirectoryFileSystem::~SubDirectoryFileSystem() {
|
||||||
|
if (this->base_path != nullptr) {
|
||||||
|
std::free(this->base_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SubDirectoryFileSystem::Initialize(const char *bp) {
|
||||||
|
/* Make sure the path isn't too long. */
|
||||||
|
R_UNLESS(strnlen(bp, fs::EntryNameLengthMax + 1) <= fs::EntryNameLengthMax, fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Normalize the path. */
|
||||||
|
char normalized_path[fs::EntryNameLengthMax + 2];
|
||||||
|
size_t normalized_path_len;
|
||||||
|
R_TRY(PathTool::Normalize(normalized_path, &normalized_path_len, bp, sizeof(normalized_path), this->IsUncPreserved()));
|
||||||
|
|
||||||
|
/* Ensure terminating '/' */
|
||||||
|
if (!PathTool::IsSeparator(normalized_path[normalized_path_len - 1])) {
|
||||||
|
AMS_ASSERT(normalized_path_len + 2 <= sizeof(normalized_path));
|
||||||
|
normalized_path[normalized_path_len] = StringTraits::DirectorySeparator;
|
||||||
|
normalized_path[normalized_path_len + 1] = StringTraits::NullTerminator;
|
||||||
|
|
||||||
|
normalized_path_len++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate new path. */
|
||||||
|
this->base_path_len = normalized_path_len + 1;
|
||||||
|
this->base_path = static_cast<char *>(std::malloc(this->base_path_len));
|
||||||
|
R_UNLESS(this->base_path != nullptr, fs::ResultAllocationFailureInSubDirectoryFileSystem());
|
||||||
|
|
||||||
|
/* Copy path in. */
|
||||||
|
std::memcpy(this->base_path, normalized_path, normalized_path_len);
|
||||||
|
this->base_path[normalized_path_len] = StringTraits::NullTerminator;
|
||||||
|
return ResultSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
Result SubDirectoryFileSystem::ResolveFullPath(char *out, size_t out_size, const char *relative_path) {
|
||||||
|
/* Ensure path will fit. */
|
||||||
|
R_UNLESS(this->base_path_len + strnlen(relative_path, fs::EntryNameLengthMax + 1) <= out_size, fs::ResultTooLongPath());
|
||||||
|
|
||||||
|
/* Copy base path. */
|
||||||
|
std::memcpy(out, this->base_path, this->base_path_len);
|
||||||
|
|
||||||
|
/* Normalize it. */
|
||||||
|
const size_t prefix_size = this->base_path_len - 2;
|
||||||
|
size_t normalized_len;
|
||||||
|
return PathTool::Normalize(out + prefix_size, &normalized_len, relative_path, out_size - prefix_size, this->IsUncPreserved());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,7 @@
|
||||||
#include "os_waitable_holder_of_inter_process_event.hpp"
|
#include "os_waitable_holder_of_inter_process_event.hpp"
|
||||||
#include "os_waitable_holder_of_interrupt_event.hpp"
|
#include "os_waitable_holder_of_interrupt_event.hpp"
|
||||||
#include "os_waitable_holder_of_thread.hpp"
|
#include "os_waitable_holder_of_thread.hpp"
|
||||||
|
#include "os_waitable_holder_of_semaphore.hpp"
|
||||||
#include "os_waitable_holder_of_message_queue.hpp"
|
#include "os_waitable_holder_of_message_queue.hpp"
|
||||||
|
|
||||||
namespace ams::os::impl {
|
namespace ams::os::impl {
|
||||||
|
@ -30,6 +31,7 @@ namespace ams::os::impl {
|
||||||
TYPED_STORAGE(WaitableHolderOfInterProcessEvent) holder_of_inter_process_event_storage;
|
TYPED_STORAGE(WaitableHolderOfInterProcessEvent) holder_of_inter_process_event_storage;
|
||||||
TYPED_STORAGE(WaitableHolderOfInterruptEvent) holder_of_interrupt_event_storage;
|
TYPED_STORAGE(WaitableHolderOfInterruptEvent) holder_of_interrupt_event_storage;
|
||||||
TYPED_STORAGE(WaitableHolderOfThread) holder_of_thread_storage;
|
TYPED_STORAGE(WaitableHolderOfThread) holder_of_thread_storage;
|
||||||
|
TYPED_STORAGE(WaitableHolderOfSemaphore) holder_of_semaphore_storage;
|
||||||
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotFull) holder_of_mq_for_not_full_storage;
|
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotFull) holder_of_mq_for_not_full_storage;
|
||||||
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotEmpty) holder_of_mq_for_not_empty_storage;
|
TYPED_STORAGE(WaitableHolderOfMessageQueueForNotEmpty) holder_of_mq_for_not_empty_storage;
|
||||||
};
|
};
|
||||||
|
@ -43,6 +45,7 @@ namespace ams::os::impl {
|
||||||
CHECK_HOLDER(WaitableHolderOfInterProcessEvent);
|
CHECK_HOLDER(WaitableHolderOfInterProcessEvent);
|
||||||
CHECK_HOLDER(WaitableHolderOfInterruptEvent);
|
CHECK_HOLDER(WaitableHolderOfInterruptEvent);
|
||||||
CHECK_HOLDER(WaitableHolderOfThread);
|
CHECK_HOLDER(WaitableHolderOfThread);
|
||||||
|
CHECK_HOLDER(WaitableHolderOfSemaphore);
|
||||||
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotFull);
|
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotFull);
|
||||||
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotEmpty);
|
CHECK_HOLDER(WaitableHolderOfMessageQueueForNotEmpty);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
#include "os_waitable_holder_base.hpp"
|
||||||
|
#include "os_waitable_object_list.hpp"
|
||||||
|
|
||||||
|
namespace ams::os::impl {
|
||||||
|
|
||||||
|
class WaitableHolderOfSemaphore : public WaitableHolderOfUserObject {
|
||||||
|
private:
|
||||||
|
Semaphore *semaphore;
|
||||||
|
private:
|
||||||
|
TriBool IsSignaledImpl() const {
|
||||||
|
return this->semaphore->count > 0 ? TriBool::True : TriBool::False;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
explicit WaitableHolderOfSemaphore(Semaphore *s) : semaphore(s) { /* ... */ }
|
||||||
|
|
||||||
|
/* IsSignaled, Link, Unlink implemented. */
|
||||||
|
virtual TriBool IsSignaled() const override {
|
||||||
|
std::scoped_lock lk(this->semaphore->mutex);
|
||||||
|
return this->IsSignaledImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual TriBool LinkToObjectList() override {
|
||||||
|
std::scoped_lock lk(this->semaphore->mutex);
|
||||||
|
|
||||||
|
GetReference(this->semaphore->waitlist).LinkWaitableHolder(*this);
|
||||||
|
return this->IsSignaledImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void UnlinkFromObjectList() override {
|
||||||
|
std::scoped_lock lk(this->semaphore->mutex);
|
||||||
|
|
||||||
|
GetReference(this->semaphore->waitlist).UnlinkWaitableHolder(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
85
stratosphere/libstratosphere/source/os/os_semaphore.cpp
Normal file
85
stratosphere/libstratosphere/source/os/os_semaphore.cpp
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms and conditions of the GNU General Public License,
|
||||||
|
* version 2, as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
#include "impl/os_waitable_object_list.hpp"
|
||||||
|
|
||||||
|
namespace ams::os {
|
||||||
|
|
||||||
|
Semaphore::Semaphore(int c, int mc) : count(c), max_count(mc) {
|
||||||
|
new (GetPointer(this->waitlist)) impl::WaitableObjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Semaphore::~Semaphore() {
|
||||||
|
GetReference(this->waitlist).~WaitableObjectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Semaphore::Acquire() {
|
||||||
|
std::scoped_lock lk(this->mutex);
|
||||||
|
|
||||||
|
while (this->count == 0) {
|
||||||
|
this->condvar.Wait(&this->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Semaphore::TryAcquire() {
|
||||||
|
std::scoped_lock lk(this->mutex);
|
||||||
|
|
||||||
|
if (this->count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Semaphore::TimedAcquire(u64 timeout) {
|
||||||
|
std::scoped_lock lk(this->mutex);
|
||||||
|
TimeoutHelper timeout_helper(timeout);
|
||||||
|
|
||||||
|
while (this->count == 0) {
|
||||||
|
if (timeout_helper.TimedOut()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->condvar.TimedWait(&this->mutex, timeout_helper.NsUntilTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
this->count--;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Semaphore::Release() {
|
||||||
|
std::scoped_lock lk(this->mutex);
|
||||||
|
|
||||||
|
AMS_ASSERT(this->count + 1 <= this->max_count);
|
||||||
|
this->count++;
|
||||||
|
|
||||||
|
this->condvar.Signal();
|
||||||
|
GetReference(this->waitlist).SignalAllThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Semaphore::Release(int count) {
|
||||||
|
std::scoped_lock lk(this->mutex);
|
||||||
|
|
||||||
|
AMS_ASSERT(this->count + count <= this->max_count);
|
||||||
|
this->count += count;
|
||||||
|
|
||||||
|
this->condvar.Broadcast();
|
||||||
|
GetReference(this->waitlist).SignalAllThreads();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -70,6 +70,14 @@ namespace ams::os {
|
||||||
this->user_data = 0;
|
this->user_data = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaitableHolder::WaitableHolder(Semaphore *semaphore) {
|
||||||
|
/* Initialize appropriate holder. */
|
||||||
|
new (GetPointer(this->impl_storage)) impl::WaitableHolderOfSemaphore(semaphore);
|
||||||
|
|
||||||
|
/* Set user-data. */
|
||||||
|
this->user_data = 0;
|
||||||
|
}
|
||||||
|
|
||||||
WaitableHolder::WaitableHolder(MessageQueue *message_queue, MessageQueueWaitKind wait_kind) {
|
WaitableHolder::WaitableHolder(MessageQueue *message_queue, MessageQueueWaitKind wait_kind) {
|
||||||
/* Initialize appropriate holder. */
|
/* Initialize appropriate holder. */
|
||||||
switch (wait_kind) {
|
switch (wait_kind) {
|
||||||
|
|
|
@ -363,7 +363,7 @@ namespace ams::spl::smc {
|
||||||
|
|
||||||
Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) {
|
Result AtmosphereGetEmummcConfig(void *out_config, void *out_paths, u32 storage_id) {
|
||||||
const u64 paths = reinterpret_cast<u64>(out_paths);
|
const u64 paths = reinterpret_cast<u64>(out_paths);
|
||||||
AMS_ASSERT(util::IsAligned(paths, 0x1000));
|
AMS_ASSERT(util::IsAligned(paths, os::MemoryPageSize));
|
||||||
|
|
||||||
SecmonArgs args = {};
|
SecmonArgs args = {};
|
||||||
args.X[0] = static_cast<u64>(FunctionId::AtmosphereGetEmummcConfig);
|
args.X[0] = static_cast<u64>(FunctionId::AtmosphereGetEmummcConfig);
|
||||||
|
|
|
@ -54,15 +54,9 @@ namespace ams::updater {
|
||||||
|
|
||||||
/* Implementations. */
|
/* Implementations. */
|
||||||
Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) {
|
Result ValidateWorkBuffer(const void *work_buffer, size_t work_buffer_size) {
|
||||||
if (work_buffer_size < BctSize + EksSize) {
|
R_UNLESS(work_buffer_size >= BctSize + EksSize, ResultTooSmallWorkBuffer());
|
||||||
return ResultTooSmallWorkBuffer();
|
R_UNLESS(util::IsAligned(work_buffer, os::MemoryPageSize), ResultNotAlignedWorkBuffer());
|
||||||
}
|
R_UNLESS(util::IsAligned(work_buffer_size, 0x200), ResultNotAlignedWorkBuffer());
|
||||||
if (!util::IsAligned(work_buffer, 0x1000)) {
|
|
||||||
return ResultNotAlignedWorkBuffer();
|
|
||||||
}
|
|
||||||
if (util::IsAligned(work_buffer_size, 0x200)) {
|
|
||||||
return ResultNotAlignedWorkBuffer();
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
return ResultSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ namespace ams::updater {
|
||||||
|
|
||||||
Result BisSave::Initialize(void *work_buffer, size_t work_buffer_size) {
|
Result BisSave::Initialize(void *work_buffer, size_t work_buffer_size) {
|
||||||
AMS_ASSERT(work_buffer_size >= SaveSize);
|
AMS_ASSERT(work_buffer_size >= SaveSize);
|
||||||
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(work_buffer), 0x1000));
|
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(work_buffer), os::MemoryPageSize));
|
||||||
AMS_ASSERT(util::IsAligned(work_buffer_size, 0x200));
|
AMS_ASSERT(util::IsAligned(work_buffer_size, 0x200));
|
||||||
|
|
||||||
R_TRY(this->accessor.Initialize());
|
R_TRY(this->accessor.Initialize());
|
||||||
|
|
|
@ -32,7 +32,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ namespace ams::ldr {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/* Convenience defines. */
|
/* Convenience defines. */
|
||||||
constexpr size_t BaseAddressAlignment = 0x200000;
|
|
||||||
constexpr size_t SystemResourceSizeAlignment = 0x200000;
|
|
||||||
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
|
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
|
||||||
|
|
||||||
/* Types. */
|
/* Types. */
|
||||||
|
@ -416,7 +414,7 @@ namespace ams::ldr {
|
||||||
/* 3.0.0+ System Resource Size. */
|
/* 3.0.0+ System Resource Size. */
|
||||||
if (hos::GetVersion() >= hos::Version_300) {
|
if (hos::GetVersion() >= hos::Version_300) {
|
||||||
/* Validate size is aligned. */
|
/* Validate size is aligned. */
|
||||||
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, SystemResourceSizeAlignment), ResultInvalidSize());
|
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ResultInvalidSize());
|
||||||
|
|
||||||
/* Validate system resource usage. */
|
/* Validate system resource usage. */
|
||||||
if (meta->npdm->system_resource_size) {
|
if (meta->npdm->system_resource_size) {
|
||||||
|
@ -503,7 +501,7 @@ namespace ams::ldr {
|
||||||
uintptr_t aslr_slide = 0;
|
uintptr_t aslr_slide = 0;
|
||||||
uintptr_t unused_size = (aslr_size - total_size);
|
uintptr_t unused_size = (aslr_size - total_size);
|
||||||
if (out_cpi->flags & svc::CreateProcessFlag_EnableAslr) {
|
if (out_cpi->flags & svc::CreateProcessFlag_EnableAslr) {
|
||||||
aslr_slide = ams::rnd::GenerateRandomU64(unused_size / BaseAddressAlignment) * BaseAddressAlignment;
|
aslr_slide = ams::rnd::GenerateRandomU64(unused_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set out. */
|
/* Set out. */
|
||||||
|
|
|
@ -34,7 +34,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,24 +191,24 @@ namespace ams::ro::impl {
|
||||||
const u64 bss_size = header->GetBssSize();
|
const u64 bss_size = header->GetBssSize();
|
||||||
|
|
||||||
/* Validate sizes meet expected. */
|
/* Validate sizes meet expected. */
|
||||||
R_UNLESS(nro_size == expected_nro_size, ResultInvalidNro());
|
R_UNLESS(nro_size == expected_nro_size, ResultInvalidNro());
|
||||||
R_UNLESS(bss_size == expected_bss_size, ResultInvalidNro());
|
R_UNLESS(bss_size == expected_bss_size, ResultInvalidNro());
|
||||||
|
|
||||||
/* Validate all sizes are aligned. */
|
/* Validate all sizes are aligned. */
|
||||||
R_UNLESS(util::IsAligned(text_size, 0x1000), ResultInvalidNro());
|
R_UNLESS(util::IsAligned(text_size, os::MemoryPageSize), ResultInvalidNro());
|
||||||
R_UNLESS(util::IsAligned(ro_size, 0x1000), ResultInvalidNro());
|
R_UNLESS(util::IsAligned(ro_size, os::MemoryPageSize), ResultInvalidNro());
|
||||||
R_UNLESS(util::IsAligned(rw_size, 0x1000), ResultInvalidNro());
|
R_UNLESS(util::IsAligned(rw_size, os::MemoryPageSize), ResultInvalidNro());
|
||||||
R_UNLESS(util::IsAligned(bss_size, 0x1000), ResultInvalidNro());
|
R_UNLESS(util::IsAligned(bss_size, os::MemoryPageSize), ResultInvalidNro());
|
||||||
|
|
||||||
/* Validate sections are in order. */
|
/* Validate sections are in order. */
|
||||||
R_UNLESS(text_ofs <= ro_ofs, ResultInvalidNro());
|
R_UNLESS(text_ofs <= ro_ofs, ResultInvalidNro());
|
||||||
R_UNLESS(ro_ofs <= rw_ofs, ResultInvalidNro());
|
R_UNLESS(ro_ofs <= rw_ofs, ResultInvalidNro());
|
||||||
|
|
||||||
/* Validate sections are sequential and contiguous. */
|
/* Validate sections are sequential and contiguous. */
|
||||||
R_UNLESS(text_ofs == 0, ResultInvalidNro());
|
R_UNLESS(text_ofs == 0, ResultInvalidNro());
|
||||||
R_UNLESS(text_ofs + text_size == ro_ofs, ResultInvalidNro());
|
R_UNLESS(text_ofs + text_size == ro_ofs, ResultInvalidNro());
|
||||||
R_UNLESS(ro_ofs + ro_size == rw_ofs, ResultInvalidNro());
|
R_UNLESS(ro_ofs + ro_size == rw_ofs, ResultInvalidNro());
|
||||||
R_UNLESS(rw_ofs + rw_size == nro_size, ResultInvalidNro());
|
R_UNLESS(rw_ofs + rw_size == nro_size, ResultInvalidNro());
|
||||||
|
|
||||||
/* Verify NRO hash. */
|
/* Verify NRO hash. */
|
||||||
R_TRY(this->ValidateHasNroHash(header));
|
R_TRY(this->ValidateHasNroHash(header));
|
||||||
|
@ -287,10 +287,10 @@ namespace ams::ro::impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr inline Result ValidateAddressAndSize(u64 address, u64 size) {
|
constexpr inline Result ValidateAddressAndSize(u64 address, u64 size) {
|
||||||
R_UNLESS(util::IsAligned(address, 0x1000), ResultInvalidAddress());
|
R_UNLESS(util::IsAligned(address, os::MemoryPageSize), ResultInvalidAddress());
|
||||||
R_UNLESS(size != 0, ResultInvalidSize());
|
R_UNLESS(size != 0, ResultInvalidSize());
|
||||||
R_UNLESS(util::IsAligned(size, 0x1000), ResultInvalidSize());
|
R_UNLESS(util::IsAligned(size, os::MemoryPageSize), ResultInvalidSize());
|
||||||
R_UNLESS(address < address + size, ResultInvalidSize());
|
R_UNLESS(address < address + size, ResultInvalidSize());
|
||||||
return ResultSuccess();
|
return ResultSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ namespace ams::ro::impl {
|
||||||
AMS_ASSERT(context != nullptr);
|
AMS_ASSERT(context != nullptr);
|
||||||
|
|
||||||
/* Validate address. */
|
/* Validate address. */
|
||||||
R_UNLESS(util::IsAligned(nrr_address, 0x1000), ResultInvalidAddress());
|
R_UNLESS(util::IsAligned(nrr_address, os::MemoryPageSize), ResultInvalidAddress());
|
||||||
|
|
||||||
/* Check the NRR is loaded. */
|
/* Check the NRR is loaded. */
|
||||||
NrrInfo *nrr_info = nullptr;
|
NrrInfo *nrr_info = nullptr;
|
||||||
|
@ -461,7 +461,7 @@ namespace ams::ro::impl {
|
||||||
AMS_ASSERT(context != nullptr);
|
AMS_ASSERT(context != nullptr);
|
||||||
|
|
||||||
/* Validate address. */
|
/* Validate address. */
|
||||||
R_UNLESS(util::IsAligned(nro_address, 0x1000), ResultInvalidAddress());
|
R_UNLESS(util::IsAligned(nro_address, os::MemoryPageSize), ResultInvalidAddress());
|
||||||
|
|
||||||
/* Check the NRO is loaded. */
|
/* Check the NRO is loaded. */
|
||||||
NroInfo *nro_info = nullptr;
|
NroInfo *nro_info = nullptr;
|
||||||
|
|
|
@ -32,7 +32,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryBlockUnitSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,7 @@ namespace ams::spl::impl {
|
||||||
|
|
||||||
Handle g_se_das_hnd;
|
Handle g_se_das_hnd;
|
||||||
u32 g_se_mapped_work_buffer_addr;
|
u32 g_se_mapped_work_buffer_addr;
|
||||||
u8 __attribute__((aligned(0x1000))) g_work_buffer[2 * WorkBufferSizeMax];
|
alignas(os::MemoryPageSize) u8 g_work_buffer[2 * WorkBufferSizeMax];
|
||||||
|
|
||||||
os::Mutex g_async_op_lock;
|
os::Mutex g_async_op_lock;
|
||||||
|
|
||||||
|
@ -597,10 +597,10 @@ namespace ams::spl::impl {
|
||||||
/* We can only map 0x400000 aligned buffers for the SE. With that in mind, we have some math to do. */
|
/* We can only map 0x400000 aligned buffers for the SE. With that in mind, we have some math to do. */
|
||||||
const uintptr_t src_addr = reinterpret_cast<uintptr_t>(src);
|
const uintptr_t src_addr = reinterpret_cast<uintptr_t>(src);
|
||||||
const uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst);
|
const uintptr_t dst_addr = reinterpret_cast<uintptr_t>(dst);
|
||||||
const uintptr_t src_addr_page_aligned = util::AlignDown(src_addr, 0x1000);
|
const uintptr_t src_addr_page_aligned = util::AlignDown(src_addr, os::MemoryPageSize);
|
||||||
const uintptr_t dst_addr_page_aligned = util::AlignDown(dst_addr, 0x1000);
|
const uintptr_t dst_addr_page_aligned = util::AlignDown(dst_addr, os::MemoryPageSize);
|
||||||
const size_t src_size_page_aligned = util::AlignUp(src_addr + src_size, 0x1000) - src_addr_page_aligned;
|
const size_t src_size_page_aligned = util::AlignUp(src_addr + src_size, os::MemoryPageSize) - src_addr_page_aligned;
|
||||||
const size_t dst_size_page_aligned = util::AlignUp(dst_addr + dst_size, 0x1000) - dst_addr_page_aligned;
|
const size_t dst_size_page_aligned = util::AlignUp(dst_addr + dst_size, os::MemoryPageSize) - dst_addr_page_aligned;
|
||||||
const u32 src_se_map_addr = CryptAesInMapBase + (src_addr_page_aligned % DeviceAddressSpaceAlign);
|
const u32 src_se_map_addr = CryptAesInMapBase + (src_addr_page_aligned % DeviceAddressSpaceAlign);
|
||||||
const u32 dst_se_map_addr = CryptAesOutMapBase + (dst_addr_page_aligned % DeviceAddressSpaceAlign);
|
const u32 dst_se_map_addr = CryptAesOutMapBase + (dst_addr_page_aligned % DeviceAddressSpaceAlign);
|
||||||
const u32 src_se_addr = CryptAesInMapBase + (src_addr % DeviceAddressSpaceAlign);
|
const u32 src_se_addr = CryptAesInMapBase + (src_addr % DeviceAddressSpaceAlign);
|
||||||
|
|
|
@ -39,7 +39,7 @@ extern "C" {
|
||||||
void __appExit(void);
|
void __appExit(void);
|
||||||
|
|
||||||
/* Exception handling. */
|
/* Exception handling. */
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue