diff --git a/stratosphere/TioServer/source/tio_file_server_packet.hpp b/stratosphere/TioServer/source/tio_file_server_packet.hpp index 17af28f70..bfca282b7 100644 --- a/stratosphere/TioServer/source/tio_file_server_packet.hpp +++ b/stratosphere/TioServer/source/tio_file_server_packet.hpp @@ -63,4 +63,125 @@ namespace ams::tio { }; static_assert(sizeof(FileServerRequestHeader) == sizeof(FileServerResponseHeader)); -} \ No newline at end of file + struct CreateDirectoryParam { + u32 path_len; + char path[]; + }; + + struct DeleteDirectoryParam { + u32 path_len; + char path[]; + }; + + struct DeleteDirectoryRecursivelyParam { + u32 path_len; + char path[]; + }; + + struct OpenDirectoryParam { + u32 path_len; + fs::OpenDirectoryMode open_mode; + char path[]; + }; + static_assert(sizeof(OpenDirectoryParam) == 0x8); + + struct CloseDirectoryParam { + u64 handle; + }; + + struct RenameDirectoryParam { + u32 old_len; + u32 new_len; + char data[]; + }; + + struct CreateFileParam { + s64 size; + u32 path_len; + fs::CreateOption option; + char path[]; + }; + static_assert(sizeof(CreateFileParam) == 0x10); + + struct DeleteFileParam { + u32 path_len; + char path[]; + }; + + struct OpenFileParam { + u32 path_len; + fs::OpenMode mode; + char path[]; + }; + static_assert(sizeof(OpenFileParam) == 0x8); + + struct FlushFileParam { + u64 handle; + }; + + struct CloseFileParam { + u64 handle; + }; + + struct RenameFileParam { + u32 old_len; + u32 new_len; + char data[]; + }; + + struct ReadFileParam { + u64 handle; + s64 offset; + u64 size; + fs::ReadOption option; + }; + static_assert(sizeof(ReadFileParam) == 0x20); + + struct WriteFileParam { + u64 handle; + s64 offset; + u64 size; + fs::WriteOption option; + }; + static_assert(sizeof(WriteFileParam) == 0x20); + + struct GetEntryTypeParam { + u32 path_len; + char path[]; + }; + + struct ReadDirectoryParam { + u64 handle; + s64 count; + }; + + struct GetFileSizeParam { + u64 handle; + }; + + struct SetFileSizeParam { + u64 handle; + s64 size; + }; + + struct GetTotalSpaceSizeParam { + u32 path_len; + char path[]; + }; + + struct GetFreeSpaceSizeParam { + u32 path_len; + char path[]; + }; + + struct StatParam { + u32 path_len; + char path[]; + }; + + struct ListDirectoryParam { + u32 path_len; + char path[]; + }; + +} diff --git a/stratosphere/TioServer/source/tio_file_server_processor.cpp b/stratosphere/TioServer/source/tio_file_server_processor.cpp index 5618f1854..05deb6c25 100644 --- a/stratosphere/TioServer/source/tio_file_server_processor.cpp +++ b/stratosphere/TioServer/source/tio_file_server_processor.cpp @@ -29,16 +29,28 @@ namespace ams::tio { 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] = {}; + if (m_open_directory_count > 0) { + for (size_t i = 0; i < util::size(m_directories); ++i) { + if (m_directories[i].handle != nullptr) { + fs::CloseDirectory(m_directories[i]); + m_directories[i] = {}; + --m_open_directory_count; + } + } } + AMS_ABORT_UNLESS(m_open_directory_count == 0); /* Close all our files. */ - for (size_t i = 0; i < m_open_file_count; ++i) { - fs::CloseFile(m_files[i]); - m_files[i] = {}; + if (m_open_file_count > 0) { + for (size_t i = 0; i < util::size(m_files); ++i) { + if (m_files[i].handle != nullptr) { + fs::CloseFile(m_files[i]); + m_files[i] = {}; + --m_open_file_count; + } + } } + AMS_ABORT_UNLESS(m_open_file_count == 0); /* If we're mounted, unmount the sd card. */ if (m_is_mounted) { @@ -85,35 +97,621 @@ namespace ams::tio { 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; - //} + /* The SD card must be inserted and mounted for us to process requests. */ + if (m_is_inserted && m_is_mounted) { + switch (header->packet_type) { + case PacketType::CreateDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Create the directory. */ + response_header.result = fs::CreateDirectory(param->path); + } + break; + case PacketType::DeleteDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the directory. */ + response_header.result = fs::DeleteDirectory(param->path); + } + break; + case PacketType::DeleteDirectoryRecursively: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the directory. */ + response_header.result = fs::DeleteDirectoryRecursively(param->path); + } + break; + case PacketType::OpenDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the directory. */ + fs::DirectoryHandle handle; + response_header.result = fs::OpenDirectory(std::addressof(handle), param->path, param->open_mode); + if (R_SUCCEEDED(response_header.result)) { + std::scoped_lock lk(m_mutex); + + if (m_open_directory_count < util::size(m_directories)) { + /* Insert the directory into our table. */ + u64 index = std::numeric_limits::max(); + for (size_t i = 0; i < util::size(m_directories); ++i) { + if (m_directories[i].handle == nullptr) { + m_directories[i] = handle; + index = i; + ++m_open_directory_count; + break; + } + } + AMS_ABORT_UNLESS(index < util::size(m_directories)); + + /* Return the index. */ + response_header.body_size = sizeof(index); + std::memcpy(body, std::addressof(index), sizeof(index)); + } else { + /* We can't actually open the directory. */ + fs::CloseDirectory(handle); + + response_header.result = fs::ResultOpenCountLimit(); + } + } + } + break; + case PacketType::CloseDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the directory handle is valid. */ + if (param->handle >= util::size(m_directories) || m_directories[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Close the directory. */ + fs::CloseDirectory(m_directories[param->handle]); + m_directories[param->handle].handle = {}; + --m_open_directory_count; + } + break; + case PacketType::RenameDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->old_len + param->new_len) { + return false; + } + + /* Delete the directory. */ + const char *old_path = param->data + 0; + const char *new_path = param->data + param->old_len; + response_header.result = fs::RenameDirectory(old_path, new_path); + } + break; + case PacketType::CreateFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Create the file. */ + response_header.result = fs::CreateFile(param->path, param->size, static_cast(param->option)); + } + break; + case PacketType::DeleteFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Delete the file. */ + response_header.result = fs::DeleteFile(param->path); + } + break; + case PacketType::OpenFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the file. */ + fs::FileHandle handle; + response_header.result = fs::OpenFile(std::addressof(handle), param->path, param->mode); + if (R_SUCCEEDED(response_header.result)) { + std::scoped_lock lk(m_mutex); + + if (m_open_file_count < util::size(m_files)) { + /* Insert the file into our table. */ + u64 index = std::numeric_limits::max(); + for (size_t i = 0; i < util::size(m_files); ++i) { + if (m_files[i].handle == nullptr) { + m_files[i] = handle; + index = i; + ++m_open_file_count; + break; + } + } + AMS_ABORT_UNLESS(index < util::size(m_files)); + + /* Return the index. */ + response_header.body_size = sizeof(index); + std::memcpy(body, std::addressof(index), sizeof(index)); + } else { + /* We can't actually open the file. */ + fs::CloseFile(handle); + + response_header.result = fs::ResultOpenCountLimit(); + } + } + } + break; + case PacketType::FlushFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Flush the file. */ + response_header.result = fs::FlushFile(m_files[param->handle]); + } + break; + case PacketType::CloseFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Close the directory. */ + fs::CloseFile(m_files[param->handle]); + m_files[param->handle].handle = {}; + --m_open_file_count; + } + break; + case PacketType::RenameFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->old_len + param->new_len) { + return false; + } + + /* Delete the directory. */ + const char *old_path = param->data + 0; + const char *new_path = param->data + param->old_len; + response_header.result = fs::RenameFile(old_path, new_path); + } + break; + case PacketType::ReadFile: + { + /* Get the parameters. */ + const auto param = *reinterpret_cast(body); + if (header->body_size != sizeof(param)) { + return false; + } + + /* Check that the read is valid. */ + if (param.size + sizeof(u64) > m_request_buffer_size) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Prepare response variables. */ + u64 *out_size = reinterpret_cast(body); + void *dst = out_size + 1; + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param.handle >= util::size(m_files) || m_files[param.handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Read the file. */ + size_t read_size; + response_header.result = fs::ReadFile(std::addressof(read_size), m_files[param.handle], param.offset, dst, param.size); + + if (R_SUCCEEDED(response_header.result)) { + *out_size = read_size; + response_header.body_size = sizeof(u64) + read_size; + } + } + break; + case PacketType::WriteFile: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->size) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Write the file. */ + response_header.result = fs::WriteFile(m_files[param->handle], param->offset, body + sizeof(*param), param->size, param->option); + } + break; + case PacketType::GetEntryType: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the entry type. */ + fs::DirectoryEntryType type; + response_header.result = fs::GetEntryType(std::addressof(type), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the type. */ + response_header.body_size = sizeof(type); + std::memcpy(body, std::addressof(type), sizeof(type)); + + static_assert(sizeof(type) == sizeof(u32)); + } + } + break; + case PacketType::ReadDirectory: + { + /* Get the parameters. */ + const auto param = *reinterpret_cast(body); + if (header->body_size != sizeof(param)) { + return false; + } + + /* Check that the read is valid. */ + if (sizeof(s64) + param.count * sizeof(fs::DirectoryEntry) > m_request_buffer_size) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Prepare response variables. */ + s64 *out_count = reinterpret_cast(body); + fs::DirectoryEntry *dst = reinterpret_cast(out_count + 1); + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the directory handle is valid. */ + if (param.handle >= util::size(m_directories) || m_directories[param.handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Read the directory. */ + response_header.result = fs::ReadDirectory(out_count, dst, m_directories[param.handle], param.count); + + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(s64) + *out_count * sizeof(fs::DirectoryEntry); + } + } + break; + case PacketType::GetFileSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Get the file size. */ + response_header.result = fs::GetFileSize(reinterpret_cast(body), m_files[param->handle]); + + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(s64); + } + } + break; + case PacketType::SetFileSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param)) { + return false; + } + + /* Lock ourselves. */ + std::scoped_lock lk(m_mutex); + + /* Check that the file handle is valid. */ + if (param->handle >= util::size(m_files) || m_files[param->handle].handle == nullptr) { + response_header.result = fs::ResultDataCorrupted(); + break; + } + + /* Lock the filesystem. */ + std::scoped_lock lk2(m_fs_mutex); + + /* Get the file size. */ + response_header.result = fs::SetFileSize(m_files[param->handle], param->size); + } + break; + case PacketType::GetTotalSpaceSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the total space size. */ + s64 size; + response_header.result = fs::GetTotalSpaceSize(std::addressof(size), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the size. */ + response_header.body_size = sizeof(size); + std::memcpy(body, std::addressof(size), sizeof(size)); + } + } + break; + case PacketType::GetFreeSpaceSize: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Get the free space size. */ + s64 size; + response_header.result = fs::GetFreeSpaceSize(std::addressof(size), param->path); + + if (R_SUCCEEDED(response_header.result)) { + /* Return the size. */ + response_header.body_size = sizeof(size); + std::memcpy(body, std::addressof(size), sizeof(size)); + } + } + break; + case PacketType::Stat: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Prepare a response stat structure. */ + struct { + fs::DirectoryEntryType type; + s64 file_size; + fs::FileTimeStampRaw file_timestamp; + } out = {}; + static_assert(sizeof(out) == 0x30); + + /* Get the entry type. */ + response_header.result = fs::GetEntryType(std::addressof(out.type), param->path); + if (R_FAILED(response_header.result)) { + break; + } + + /* If the path is a file, get further information. */ + if (out.type == fs::DirectoryEntryType_File) { + /* Try to get the file size. */ + { + fs::FileHandle handle; + const auto open_result = fs::OpenFile(std::addressof(handle), param->path, fs::OpenMode_Read); + if (R_SUCCEEDED(open_result)) { + ON_SCOPE_EXIT { fs::CloseFile(handle); }; + + response_header.result = fs::GetFileSize(std::addressof(out.file_size), handle); + if (R_FAILED(response_header.result)) { + break; + } + } else { + if (fs::ResultTargetLocked::Includes(open_result)) { + out.file_size = 0; + } else { + response_header.result = open_result; + break; + } + } + } + + /* Get the file timestamp. */ + response_header.result = fs::GetFileTimeStampRawForDebug(std::addressof(out.file_timestamp), param->path); + if (R_FAILED(response_header.result)) { + break; + } + } + + /* If we successfully got the stat information, send it as response. */ + if (R_SUCCEEDED(response_header.result)) { + response_header.body_size = sizeof(out); + std::memcpy(body, std::addressof(out), sizeof(out)); + } + } + break; + case PacketType::ListDirectory: + { + /* Get the parameters. */ + const auto *param = reinterpret_cast(body); + if (header->body_size != sizeof(*param) + param->path_len) { + return false; + } + + /* Open the directory. */ + fs::DirectoryHandle handle; + response_header.result = fs::OpenDirectory(std::addressof(handle), param->path, fs::OpenDirectoryMode_All); + if (R_FAILED(response_header.result)) { + break; + } + + /* When we're done, close the handle. */ + ON_SCOPE_EXIT { fs::CloseDirectory(handle); }; + + /* Get the directory entry count. */ + s64 count; + response_header.result = fs::GetDirectoryEntryCount(std::addressof(count), handle); + if (R_FAILED(response_header.result)) { + break; + } + /* Determine whether we can send the response in one go. */ + const size_t needed_size = sizeof(s64) + sizeof(u64) + sizeof(fs::DirectoryEntry) * count; + if (needed_size <= m_request_buffer_size) { + /* We can perform the entire read in one send. */ + struct { + s64 count; + u64 size; + fs::DirectoryEntry entries[]; + } *out = reinterpret_cast(body); + + s64 read_count; + response_header.result = fs::ReadDirectory(std::addressof(read_count), out->entries, handle, count); + if (R_FAILED(response_header.result)) { + break; + } + + /* Set the output. */ + out->count = read_count; + out->size = read_count * sizeof(fs::DirectoryEntry); + + /* Set the response body size. */ + response_header.body_size = sizeof(out) + out->size; + } else { + /* We have to use multiple sends. */ + /* Lock our server. */ + std::scoped_lock lk(m_htcs_server.GetMutex()); + + /* Send the response header. */ + response_header.body_size = needed_size; + if (m_htcs_server.Send(socket, std::addressof(header), sizeof(header), 0) != sizeof(header)) { + return false; + } + + /* Send the body header. */ + struct { + s64 count; + u64 size; + } out = { count, count * sizeof(fs::DirectoryEntry) }; + if (m_htcs_server.Send(socket, std::addressof(out), sizeof(out), 0) != sizeof(out)) { + return false; + } + + /* Loop sending entries. */ + s64 remaining = count; + do { + /* Determine how many entries we can read. */ + const s64 cur = std::min(remaining, static_cast(m_request_buffer_size / sizeof(fs::DirectoryEntry))); + + /* NOTE: Nintendo does not check the output of this call. */ + s64 read_count = 0; + fs::ReadDirectory(std::addressof(read_count), reinterpret_cast(body), handle, cur); + + /* Send the current entries. */ + const ssize_t cur_size = read_count * sizeof(fs::DirectoryEntry); + if (m_htcs_server.Send(socket, body, cur_size, 0) != cur_size) { + return false; + } + + /* Advance. */ + remaining -= read_count; + } while (remaining > 0); + + /* We've sent the entirety of our response, so early return. */ + return true; + } + } + break; + default: + /* Unsupported packet. */ + return false; + } + + /* Send the response. */ + return this->SendResponse(response_header, body, socket); + } else if (m_is_mounted) { + /* The SD card is mounted but not inserted, so we should unmount it. */ + this->Unmount(); + } + + /* We failed to process the request due to SD card not being inserted or mounted. */ + response_header.result = fs::ResultSdCardAccessFailed(); + + return this->SendResponse(response_header, body, socket); } bool FileServerProcessor::SendResponse(const FileServerResponseHeader &header, const void *body, int socket) { @@ -134,4 +732,4 @@ namespace ams::tio { return m_htcs_server.Send(socket, body, header.body_size, 0) == header.body_size; } -} \ No newline at end of file +}