diff --git a/stratosphere/TioServer/source/tio_file_server.cpp b/stratosphere/TioServer/source/tio_file_server.cpp new file mode 100644 index 000000000..8c02dcfab --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#include +#include "tio_file_server.hpp" +#include "tio_file_server_packet.hpp" +#include "tio_file_server_htcs_server.hpp" +#include "tio_file_server_processor.hpp" + +namespace ams::tio { + + namespace { + + constexpr inline auto NumDispatchThreads = 2; + constexpr inline auto DispatchThreadPriority = 21; + constexpr inline size_t RequestBufferSize = 1_MB + util::AlignUp(0x40 + fs::EntryNameLengthMax, 1_KB); + + struct FileServerRequest { + int socket; + FileServerRequestHeader header; + u8 body[RequestBufferSize]; + }; + + constexpr const char HtcsPortName[] = "iywys@$TioServer_FileServer"; + + alignas(os::ThreadStackAlignment) u8 g_server_stack[os::MemoryPageSize]; + alignas(os::ThreadStackAlignment) u8 g_dispatch_stacks[NumDispatchThreads][os::MemoryPageSize]; + + constinit FileServerHtcsServer g_file_server_htcs_server; + constinit FileServerProcessor g_file_server_processor(g_file_server_htcs_server); + + constinit os::ThreadType g_file_server_dispatch_threads[NumDispatchThreads]; + + constinit FileServerRequest g_requests[NumDispatchThreads]; + + constinit os::MessageQueueType g_free_mq; + constinit os::MessageQueueType g_dispatch_mq; + + constinit uintptr_t g_free_mq_storage[NumDispatchThreads]; + constinit uintptr_t g_dispatch_mq_storage[NumDispatchThreads]; + + void OnFileServerHtcsSocketAccepted(int fd) { + /* Service requests, while we can. */ + while (true) { + /* Receive a free request. */ + uintptr_t request_address; + os::ReceiveMessageQueue(std::addressof(request_address), std::addressof(g_free_mq)); + + /* Ensure we manage our request properly. */ + auto req_guard = SCOPE_GUARD { os::SendMessageQueue(std::addressof(g_free_mq), request_address); }; + + /* Receive the request header. */ + FileServerRequest *request = reinterpret_cast(request_address); + if (htcs::Recv(fd, std::addressof(request->header), sizeof(request->header), htcs::HTCS_MSG_WAITALL) != sizeof(request->header)) { + break; + } + + /* Receive the request body, if necessary. */ + if (request->header.body_size > 0) { + if (htcs::Recv(fd, request->body, request->header.body_size, htcs::HTCS_MSG_WAITALL) != sizeof(request->header.body_size)) { + break; + } + } + + /* Dispatch the request. */ + req_guard.Cancel(); + request->socket = fd; + os::SendMessageQueue(std::addressof(g_dispatch_mq), request_address); + } + + /* Our socket is no longer making requests, so close it. */ + htcs::Close(fd); + + /* Clean up any server resources. */ + g_file_server_processor.Unmount(); + } + + void FileServerDispatchThreadFunction(void *) { + while (true) { + /* Receive a request. */ + uintptr_t request_address; + os::ReceiveMessageQueue(std::addressof(request_address), std::addressof(g_dispatch_mq)); + + /* Process the request. */ + FileServerRequest *request = reinterpret_cast(request_address); + if (!g_file_server_processor.ProcessRequest(std::addressof(request->header), request->body, request->socket)) { + htcs::Close(request->socket); + } + + /* Free the request. */ + os::SendMessageQueue(std::addressof(g_free_mq), request_address); + } + } + + } + + void InitializeFileServer() { + /* Initialize the htcs server. */ + g_file_server_htcs_server.Initialize(HtcsPortName, g_server_stack, sizeof(g_server_stack), OnFileServerHtcsSocketAccepted); + + /* TODO: Initialize SD card observer. */ + + /* Initialize the command processor. */ + g_file_server_processor.SetInserted(false); + g_file_server_processor.SetRequestBufferSize(RequestBufferSize); + + /* Initialize the dispatch message queues. */ + os::InitializeMessageQueue(std::addressof(g_free_mq), g_free_mq_storage, util::size(g_free_mq_storage)); + os::InitializeMessageQueue(std::addressof(g_dispatch_mq), g_dispatch_mq_storage, util::size(g_dispatch_mq_storage)); + + /* Begin with all requests free. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + os::SendMessageQueue(std::addressof(g_free_mq), reinterpret_cast(g_requests + i)); + } + + /* Initialize the dispatch threads. */ + /* NOTE: Nintendo does not name these threads. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + R_ABORT_UNLESS(os::CreateThread(g_file_server_dispatch_threads + i, FileServerDispatchThreadFunction, nullptr, g_dispatch_stacks + i, sizeof(g_dispatch_stacks[i]), DispatchThreadPriority)); + } + } + + void StartFileServer() { + /* Start the htcs server. */ + g_file_server_htcs_server.Start(); + + /* Start the dispatch threads. */ + for (auto i = 0; i < NumDispatchThreads; ++i) { + os::StartThread(g_file_server_dispatch_threads + i); + } + } + + void WaitFileServer() { + /* Wait for the htcs server to finish. */ + g_file_server_htcs_server.Wait(); + } + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server.hpp b/stratosphere/TioServer/source/tio_file_server.hpp new file mode 100644 index 000000000..f20803042 --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#pragma once +#include + +namespace ams::tio { + + void InitializeFileServer(); + void StartFileServer(); + void WaitFileServer(); + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp b/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp new file mode 100644 index 000000000..91b65b2d0 --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server_htcs_server.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#include +#include "tio_file_server_htcs_server.hpp" + +namespace ams::tio { + + void FileServerHtcsServer::Initialize(const char *port_name, void *thread_stack, size_t thread_stack_size, SocketAcceptedCallback on_socket_accepted) { + /* Set our port name. */ + std::strcpy(m_port_name.name, port_name); + + /* Set our callback. */ + m_on_socket_accepted = on_socket_accepted; + + /* Setup our thread. */ + R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ThreadEntry, this, thread_stack, thread_stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(TioServer, FileServerHtcsServer))); + + /* Set our thread name pointer. */ + os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(TioServer, FileServerHtcsServer)); + } + + void FileServerHtcsServer::Start() { + os::StartThread(std::addressof(m_thread)); + } + void FileServerHtcsServer::Wait() { + os::WaitThread(std::addressof(m_thread)); + } + + void FileServerHtcsServer::ThreadFunc() { + /* Loop forever, servicing sockets. */ + while (true) { + /* Get a socket. */ + int fd; + while ((fd = htcs::Socket()) == -1) { + os::SleepThread(TimeSpan::FromSeconds(1)); + } + + /* Ensure we cleanup the socket when we're done with it. */ + ON_SCOPE_EXIT { + htcs::Close(fd); + os::SleepThread(TimeSpan::FromSeconds(1)); + }; + + /* Create a sock addr for our server. */ + htcs::SockAddrHtcs addr; + addr.family = htcs::HTCS_AF_HTCS; + addr.peer_name = htcs::GetPeerNameAny(); + addr.port_name = m_port_name; + + /* Bind. */ + if (htcs::Bind(fd, std::addressof(addr)) == -1) { + continue; + } + + /* Listen on our port. */ + while (htcs::Listen(fd, 0) == 0) { + /* Continue accepting clients, so long as we can. */ + int client_fd; + while (true) { + /* Try to accept a client. */ + if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) { + break; + } + + /* Handle the client. */ + m_on_socket_accepted(client_fd); + } + + /* NOTE: This seems unnecessary (client_fd guaranteed < 0 here), but Nintendo does it. */ + htcs::Close(client_fd); + } + } + } + + ssize_t FileServerHtcsServer::Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags) { + AMS_ASSERT(m_mutex.IsLockedByCurrentThread()); + + return htcs::Send(desc, buffer, buffer_size, flags); + } + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp b/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp new file mode 100644 index 000000000..b1ab62f2d --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server_htcs_server.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#pragma once +#include + +namespace ams::tio { + + using SocketAcceptedCallback = void(*)(s32 desc); + + class FileServerHtcsServer { + private: + SocketAcceptedCallback m_on_socket_accepted; + htcs::HtcsPortName m_port_name; + os::ThreadType m_thread; + os::SdkMutex m_mutex; + public: + constexpr FileServerHtcsServer() : m_on_socket_accepted(nullptr), m_port_name{}, m_thread{}, m_mutex{} { /* ... */ } + private: + static void ThreadEntry(void *arg) { + static_cast(arg)->ThreadFunc(); + } + + void ThreadFunc(); + public: + os::SdkMutex &GetMutex() { return m_mutex; } + public: + void Initialize(const char *port_name, void *thread_stack, size_t thread_stack_size, SocketAcceptedCallback on_socket_accepted); + void Start(); + void Wait(); + + ssize_t Send(s32 desc, const void *buffer, size_t buffer_size, s32 flags); + }; + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server_packet.hpp b/stratosphere/TioServer/source/tio_file_server_packet.hpp new file mode 100644 index 000000000..17af28f70 --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server_packet.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#pragma once +#include + +namespace ams::tio { + + enum class PacketType : u32 { + /* Control commands. */ + Connect = 0, + Disconnect = 1, + + /* Direct filesystem access. */ + CreateDirectory = 500, + DeleteDirectory = 501, + DeleteDirectoryRecursively = 502, + OpenDirectory = 503, + CloseDirectory = 504, + RenameDirectory = 505, + CreateFile = 506, + DeleteFile = 507, + OpenFile = 508, + FlushFile = 509, + CloseFile = 510, + RenameFile = 511, + ReadFile = 512, + WriteFile = 513, + GetEntryType = 514, + ReadDirectory = 515, + GetFileSize = 516, + SetFileSize = 517, + GetTotalSpaceSize = 518, + GetFreeSpaceSize = 519, + + /* Utilities. */ + Stat = 1000, + ListDirectory = 1001, + }; + + struct FileServerRequestHeader { + u64 request_id; + PacketType packet_type; + u32 body_size; + }; + + struct FileServerResponseHeader { + u64 request_id; + Result result; + u32 body_size; + }; + static_assert(sizeof(FileServerRequestHeader) == sizeof(FileServerResponseHeader)); + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server_processor.cpp b/stratosphere/TioServer/source/tio_file_server_processor.cpp new file mode 100644 index 000000000..5618f1854 --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server_processor.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#include +#include "tio_file_server_processor.hpp" + +namespace ams::tio { + + namespace { + + constexpr inline int ProtocolVersion = 1; + + } + + void FileServerProcessor::Unmount() { + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Close all our directories. */ + for (size_t i = 0; i < m_open_directory_count; ++i) { + fs::CloseDirectory(m_directories[i]); + m_directories[i] = {}; + } + + /* Close all our files. */ + for (size_t i = 0; i < m_open_file_count; ++i) { + fs::CloseFile(m_files[i]); + m_files[i] = {}; + } + + /* If we're mounted, unmount the sd card. */ + if (m_is_mounted) { + m_is_mounted = false; + fs::Unmount("sd"); + } + } + + bool FileServerProcessor::ProcessRequest(FileServerRequestHeader *header, u8 *body, int socket) { + /* Declare a response header for us to use. */ + FileServerResponseHeader response_header = { + .request_id = header->request_id, + .result = ResultSuccess(), + .body_size = 0, + }; + + /* Handle the special control commands. */ + if (header->packet_type == PacketType::Connect) { + /* If the SD card isn't already mounted, try to mount it. */ + if (!m_is_mounted) { + /* Mount the sd card. */ + m_is_mounted = !fs::ResultSdCardAccessFailed::Includes(fs::MountSdCard("sd")); + + /* Prepare the response. */ + char *response_body = reinterpret_cast(body); + util::SNPrintf(response_body, 0x100, "{\"bufferSize\":%zu, \"sdcardMounted\":%s, \"sdcardInserted\":%s, \"version\":%d}", + m_request_buffer_size, + m_is_mounted ? "true" : "false", + m_is_inserted ? "true" : "false", + ProtocolVersion); + + /* Get the response length. */ + response_header.body_size = std::strlen(response_body); + } + + return this->SendResponse(response_header, body, socket); + } else if (header->packet_type == PacketType::Disconnect) { + /* If we need to, unmount the sd card. */ + if (m_is_mounted) { + this->Unmount(); + } + + /* Send the response. */ + return this->SendResponse(response_header, body, socket); + } + + /* TODO: Handle remaining packet types. */ + return false; + // + //switch (header->packet_type) { + // case PacketType::CreateDirectory: + // case PacketType::DeleteDirectory: + // case PacketType::DeleteDirectoryRecursively: + // case PacketType::OpenDirectory: + // case PacketType::CloseDirectory: + // case PacketType::RenameDirectory: + // case PacketType::CreateFile: + // case PacketType::DeleteFile: + // case PacketType::OpenFile: + // case PacketType::FlushFile: + // case PacketType::CloseFile: + // case PacketType::RenameFile: + // case PacketType::ReadFile: + // case PacketType::WriteFile: + // case PacketType::GetEntryType: + // case PacketType::ReadDirectory: + // case PacketType::GetFileSize: + // case PacketType::SetFileSize: + // case PacketType::GetTotalSpaceSize: + // case PacketType::GetFreeSpaceSize: + // case PacketType::Stat: + // case PacketType::ListDirectory: + // /* TODO */ + // return false; + //} + } + + bool FileServerProcessor::SendResponse(const FileServerResponseHeader &header, const void *body, int socket) { + /* Lock our server. */ + std::scoped_lock lk(m_htcs_server.GetMutex()); + + /* Send the response header. */ + if (m_htcs_server.Send(socket, std::addressof(header), sizeof(header), 0) != sizeof(header)) { + return false; + } + + /* If we don't have a body, we're done. */ + if (header.body_size == 0) { + return true; + } + + /* Send the body. */ + return m_htcs_server.Send(socket, body, header.body_size, 0) == header.body_size; + } + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_file_server_processor.hpp b/stratosphere/TioServer/source/tio_file_server_processor.hpp new file mode 100644 index 000000000..8ec5c1ac5 --- /dev/null +++ b/stratosphere/TioServer/source/tio_file_server_processor.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#pragma once +#include +#include "tio_file_server_htcs_server.hpp" +#include "tio_file_server_packet.hpp" + +namespace ams::tio { + + class FileServerProcessor { + private: + bool m_is_inserted{}; + bool m_is_mounted{}; + size_t m_request_buffer_size{}; + FileServerHtcsServer &m_htcs_server; + size_t m_open_file_count{}; + size_t m_open_directory_count{}; + fs::FileHandle m_files[0x80]{}; + fs::DirectoryHandle m_directories[0x80]{}; + os::SdkMutex m_fs_mutex{}; + os::SdkMutex m_mutex{}; + public: + constexpr FileServerProcessor(FileServerHtcsServer &htcs_server) : m_htcs_server(htcs_server) { /* ... */ } + + void SetInserted(bool ins) { m_is_inserted = ins; } + void SetRequestBufferSize(size_t size) { m_request_buffer_size = size; } + public: + bool ProcessRequest(FileServerRequestHeader *header, u8 *body, int socket); + + void Unmount(); + private: + bool SendResponse(const FileServerResponseHeader &header, const void *body, int socket); + }; + +} \ No newline at end of file diff --git a/stratosphere/TioServer/source/tio_main.cpp b/stratosphere/TioServer/source/tio_main.cpp index bd2844517..9bf97dac4 100644 --- a/stratosphere/TioServer/source/tio_main.cpp +++ b/stratosphere/TioServer/source/tio_main.cpp @@ -14,6 +14,7 @@ * along with this program. If not, see . */ #include +#include "tio_file_server.hpp" extern "C" { extern u32 __start__; @@ -61,14 +62,63 @@ void __libnx_exception_handler(ThreadExceptionDump *ctx) { #endif +namespace ams::tio { + + namespace { + + alignas(0x40) constinit u8 g_fs_heap_buffer[64_KB]; + alignas(0x40) constinit u8 g_htcs_buffer[1_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + /* Setup fs allocator. */ + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_buffer, sizeof(g_fs_heap_buffer), lmem::CreateOption_ThreadSafe); + } + + } + +} + +void __libnx_initheap(void) { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; + + ams::tio::InitializeFsHeap(); +} + void __appInit(void) { hos::InitializeForStratosphere(); - /* TODO */ + /* Initialize FS heap. */ + fs::SetAllocator(tio::AllocateForFs, tio::DeallocateForFs); + + /* Disable FS auto-abort. */ + fs::SetEnabledAutoAbort(false); + + sm::DoWithSession([&]() { + R_ABORT_UNLESS(fsInitialize()); + }); + + ams::CheckApiVersion(); } void __appExit(void) { - /* TODO */ + fsExit(); } namespace ams { @@ -109,5 +159,20 @@ int main(int argc, char **argv) os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(TioServer, Main)); AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(TioServer, Main)); + /* Initialize htcs. */ + constexpr auto HtcsSocketCountMax = 2; + const size_t buffer_size = htcs::GetWorkingMemorySize(HtcsSocketCountMax); + AMS_ABORT_UNLESS(sizeof(tio::g_htcs_buffer) >= buffer_size); + htcs::InitializeForSystem(tio::g_htcs_buffer, buffer_size, HtcsSocketCountMax); + + /* Initialize the file server. */ + tio::InitializeFileServer(); + + /* Start the file server. */ + tio::StartFileServer(); + + /* Wait for the file server to finish. */ + tio::WaitFileServer(); + return 0; }