mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-06-05 01:03:43 -04:00
LogManager: implement system module, client api, logging api (#1617)
Some notes: * Unless `atmosphere!enable_log_manager` is true, Nintendo's log manager will be used instead. * This prevents paying memory costs for LM when not enabling logging. * To facilitate this, Atmosphere's log manager has a different program id from Nintendo's. * `atmosphere!enable_htc` implies `atmosphere!enable_log_manager`. * LogManager logs to tma, and the SD card (if `lm!enable_sd_card_logging` is true, which it is by default). * Binary logs are saved to `lm!sd_card_log_output_directory`, which is `atmosphere/binlogs` by default.
This commit is contained in:
parent
a1fb8a91c8
commit
e9849c74cf
94 changed files with 5595 additions and 45 deletions
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_custom_sink_buffer.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
bool CustomSinkBuffer::TryPush(const void *data, size_t size) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(size <= m_buffer_size);
|
||||
AMS_ASSERT(data || size == 0);
|
||||
|
||||
/* If we have nothing to push, succeed. */
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Check that we can push the data. */
|
||||
if (size > m_buffer_size - m_used_buffer_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Push the data. */
|
||||
std::memcpy(m_buffer + m_used_buffer_size, data, size);
|
||||
m_used_buffer_size += size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CustomSinkBuffer::TryFlush() {
|
||||
/* Check that we have data to flush. */
|
||||
if (m_used_buffer_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to flush the data. */
|
||||
if (!m_flush_function(m_buffer, m_used_buffer_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Clear our used size. */
|
||||
m_used_buffer_size = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class CustomSinkBuffer {
|
||||
NON_COPYABLE(CustomSinkBuffer);
|
||||
NON_MOVEABLE(CustomSinkBuffer);
|
||||
public:
|
||||
using FlushFunction = bool (*)(const u8 *buffer, size_t buffer_size);
|
||||
private:
|
||||
u8 *m_buffer;
|
||||
size_t m_buffer_size;
|
||||
size_t m_used_buffer_size;
|
||||
FlushFunction m_flush_function;
|
||||
public:
|
||||
constexpr explicit CustomSinkBuffer(void *buffer, size_t buffer_size, FlushFunction f) : m_buffer(static_cast<u8 *>(buffer)), m_buffer_size(buffer_size), m_used_buffer_size(0), m_flush_function(f) {
|
||||
AMS_ASSERT(m_buffer != nullptr);
|
||||
AMS_ASSERT(m_buffer_size > 0);
|
||||
AMS_ASSERT(m_flush_function != nullptr);
|
||||
}
|
||||
|
||||
bool TryPush(const void *data, size_t size);
|
||||
bool TryFlush();
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_event_log_transmitter.hpp"
|
||||
#include "lm_log_buffer.hpp"
|
||||
#include "../impl/lm_log_packet_header.hpp"
|
||||
#include "../impl/lm_log_packet_transmitter.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline size_t TransmitterBufferAlign = alignof(impl::LogPacketHeader);
|
||||
constexpr inline size_t TransmitterBufferSizeForSessionInfo = impl::LogPacketHeaderSize + 3;
|
||||
constexpr inline size_t TransmitterBufferSizeForDropCount = impl::LogPacketHeaderSize + 10;
|
||||
|
||||
bool DefaultFlushFunction(const u8 *data, size_t size) {
|
||||
return LogBuffer::GetDefaultInstance().TryPush(data, size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
EventLogTransmitter &EventLogTransmitter::GetDefaultInstance() {
|
||||
static constinit EventLogTransmitter s_default_event_log_transmitter(DefaultFlushFunction);
|
||||
return s_default_event_log_transmitter;
|
||||
}
|
||||
|
||||
bool EventLogTransmitter::PushLogSessionBegin(u64 process_id) {
|
||||
/* Create a transmitter. */
|
||||
alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo];
|
||||
impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, process_id, true, true);
|
||||
|
||||
/* Push session begin. */
|
||||
transmitter.PushLogSessionBegin();
|
||||
|
||||
/* Flush the data. */
|
||||
const bool success = transmitter.Flush(true);
|
||||
|
||||
/* Update drop count. */
|
||||
if (!success) {
|
||||
++m_log_packet_drop_count;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EventLogTransmitter::PushLogSessionEnd(u64 process_id) {
|
||||
/* Create a transmitter. */
|
||||
alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForSessionInfo];
|
||||
impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, process_id, true, true);
|
||||
|
||||
/* Push session end. */
|
||||
transmitter.PushLogSessionEnd();
|
||||
|
||||
/* Flush the data. */
|
||||
const bool success = transmitter.Flush(true);
|
||||
|
||||
/* Update drop count. */
|
||||
if (!success) {
|
||||
++m_log_packet_drop_count;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
bool EventLogTransmitter::PushLogPacketDropCountIfExists() {
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_log_packet_drop_count_mutex);
|
||||
|
||||
/* If we have no dropped packets, nothing to push. */
|
||||
if (m_log_packet_drop_count == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Create a transmitter. */
|
||||
alignas(TransmitterBufferAlign) u8 buffer[TransmitterBufferSizeForDropCount];
|
||||
impl::LogPacketTransmitter transmitter(buffer, sizeof(buffer), m_flush_function, static_cast<u8>(diag::LogSeverity_Info), 0, 0, true, true);
|
||||
|
||||
/* Push log packet drop count. */
|
||||
transmitter.PushLogPacketDropCount(m_log_packet_drop_count);
|
||||
|
||||
/* Flush the data. */
|
||||
const bool success = transmitter.Flush(true);
|
||||
|
||||
/* Update drop count. */
|
||||
if (success) {
|
||||
m_log_packet_drop_count = 0;
|
||||
} else {
|
||||
++m_log_packet_drop_count;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void EventLogTransmitter::IncreaseLogPacketDropCount() {
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_log_packet_drop_count_mutex);
|
||||
|
||||
/* Increase the dropped packet count. */
|
||||
++m_log_packet_drop_count;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class EventLogTransmitter {
|
||||
NON_COPYABLE(EventLogTransmitter);
|
||||
NON_MOVEABLE(EventLogTransmitter);
|
||||
public:
|
||||
using FlushFunction = bool (*)(const u8 *data, size_t size);
|
||||
private:
|
||||
FlushFunction m_flush_function;
|
||||
size_t m_log_packet_drop_count;
|
||||
os::SdkMutex m_log_packet_drop_count_mutex;
|
||||
public:
|
||||
constexpr explicit EventLogTransmitter(FlushFunction f) : m_flush_function(f), m_log_packet_drop_count(0), m_log_packet_drop_count_mutex() {
|
||||
AMS_ASSERT(f != nullptr);
|
||||
}
|
||||
|
||||
static EventLogTransmitter &GetDefaultInstance();
|
||||
|
||||
bool PushLogSessionBegin(u64 process_id);
|
||||
bool PushLogSessionEnd(u64 process_id);
|
||||
|
||||
bool PushLogPacketDropCountIfExists();
|
||||
|
||||
void IncreaseLogPacketDropCount();
|
||||
};
|
||||
|
||||
}
|
179
libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp
Normal file
179
libraries/libstratosphere/source/lm/srv/lm_flush_thread.cpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_server_proxy.hpp"
|
||||
#include "lm_sd_card_logger.hpp"
|
||||
#include "lm_log_buffer.hpp"
|
||||
#include "lm_event_log_transmitter.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
bool IsSleeping();
|
||||
|
||||
namespace {
|
||||
|
||||
alignas(os::ThreadStackAlignment) u8 g_flush_thread_stack[8_KB];
|
||||
|
||||
constinit u8 g_fs_heap[32_KB];
|
||||
constinit lmem::HeapHandle g_fs_heap_handle;
|
||||
|
||||
constinit os::ThreadType g_flush_thread;
|
||||
|
||||
os::Event g_stop_event(os::EventClearMode_ManualClear);
|
||||
os::Event g_sd_logging_event(os::EventClearMode_ManualClear);
|
||||
os::Event g_host_connection_event(os::EventClearMode_ManualClear);
|
||||
|
||||
constinit std::unique_ptr<fs::IEventNotifier> g_sd_card_detection_event_notifier;
|
||||
os::SystemEvent g_sd_card_detection_event;
|
||||
|
||||
void *AllocateForFs(size_t size) {
|
||||
return lmem::AllocateFromExpHeap(g_fs_heap_handle, size);
|
||||
}
|
||||
|
||||
void DeallocateForFs(void *ptr, size_t size) {
|
||||
return lmem::FreeToExpHeap(g_fs_heap_handle, ptr);
|
||||
}
|
||||
|
||||
void HostConnectionObserver(bool is_connected) {
|
||||
/* Update the host connection event. */
|
||||
if (is_connected) {
|
||||
g_host_connection_event.Signal();
|
||||
} else {
|
||||
g_host_connection_event.Clear();
|
||||
|
||||
/* Potentially cancel the log buffer push. */
|
||||
if (!g_sd_logging_event.TryWait()) {
|
||||
LogBuffer::GetDefaultInstance().CancelPush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SdLoggingObserver(bool is_available) {
|
||||
/* Update the SD card logging event. */
|
||||
if (is_available) {
|
||||
g_sd_logging_event.Signal();
|
||||
} else {
|
||||
g_sd_logging_event.Clear();
|
||||
|
||||
/* Potentially cancel the log buffer push. */
|
||||
if (!g_host_connection_event.TryWait()) {
|
||||
LogBuffer::GetDefaultInstance().CancelPush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WaitForFlush() {
|
||||
while (true) {
|
||||
/* Wait for something to be signaled. */
|
||||
os::WaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase(), g_sd_card_detection_event.GetBase());
|
||||
|
||||
/* If we're stopping, no flush. */
|
||||
if (g_stop_event.TryWait()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If host is connected/we're logging to sd, flush. */
|
||||
if (g_host_connection_event.TryWait() || g_sd_logging_event.TryWait()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If the sd card is newly inserted, flush. */
|
||||
if (g_sd_card_detection_event.TryWait()) {
|
||||
g_sd_card_detection_event.Clear();
|
||||
|
||||
if (fs::IsSdCardInserted()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlushThreadFunction(void *) {
|
||||
/* Disable abort. */
|
||||
fs::SetEnabledAutoAbort(false);
|
||||
|
||||
/* Create fs heap. */
|
||||
g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap, sizeof(g_fs_heap), lmem::CreateOption_None);
|
||||
AMS_ABORT_UNLESS(g_fs_heap_handle != nullptr);
|
||||
|
||||
/* Set fs allocation functions. */
|
||||
fs::SetAllocator(AllocateForFs, DeallocateForFs);
|
||||
|
||||
/* Create SD card detection event notifier. */
|
||||
R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier)));
|
||||
R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear));
|
||||
|
||||
/* Set connection observers. */
|
||||
SdCardLogger::GetInstance().SetLoggingObserver(SdLoggingObserver);
|
||||
LogServerProxy::GetInstance().SetConnectionObserver(HostConnectionObserver);
|
||||
|
||||
/* Do flush loop. */
|
||||
do {
|
||||
if (LogBuffer::GetDefaultInstance().Flush()) {
|
||||
EventLogTransmitter::GetDefaultInstance().PushLogPacketDropCountIfExists();
|
||||
}
|
||||
} while (WaitForFlush());
|
||||
|
||||
/* Clear connection observer. */
|
||||
LogServerProxy::GetInstance().SetConnectionObserver(nullptr);
|
||||
|
||||
/* Finalize the SD card logger. */
|
||||
SdCardLogger::GetInstance().Finalize();
|
||||
SdCardLogger::GetInstance().SetLoggingObserver(nullptr);
|
||||
|
||||
/* Destroy the fs heap. */
|
||||
lmem::DestroyExpHeap(g_fs_heap_handle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IsFlushAvailable() {
|
||||
/* If we're sleeping, we can't flush. */
|
||||
if (IsSleeping()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to wait for an event. */
|
||||
if (os::TryWaitAny(g_stop_event.GetBase(), g_host_connection_event.GetBase(), g_sd_logging_event.GetBase()) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return whether we're not stopping. */
|
||||
return !os::TryWaitEvent(g_stop_event.GetBase());
|
||||
}
|
||||
|
||||
void InitializeFlushThread() {
|
||||
/* Create the flush thread. */
|
||||
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_flush_thread), FlushThreadFunction, nullptr, g_flush_thread_stack, sizeof(g_flush_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, Flush)));
|
||||
os::SetThreadNamePointer(std::addressof(g_flush_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, Flush));
|
||||
|
||||
/* Clear the stop event. */
|
||||
g_stop_event.Clear();
|
||||
|
||||
/* Start the flush thread. */
|
||||
os::StartThread(std::addressof(g_flush_thread));
|
||||
}
|
||||
|
||||
void FinalizeFlushThread() {
|
||||
/* Signal the flush thread to stop. */
|
||||
g_stop_event.Signal();
|
||||
|
||||
/* Wait for the flush thread to stop. */
|
||||
os::WaitThread(std::addressof(g_flush_thread));
|
||||
os::DestroyThread(std::addressof(g_flush_thread));
|
||||
}
|
||||
|
||||
}
|
145
libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp
Normal file
145
libraries/libstratosphere/source/lm/srv/lm_ipc_server.cpp
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "../lm_service_name.hpp"
|
||||
#include "lm_log_service_impl.hpp"
|
||||
#include "lm_log_getter.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline size_t LogSessionCountMax = 42;
|
||||
constexpr inline size_t LogGetterSessionCountMax = 1;
|
||||
|
||||
constexpr inline size_t SessionCountMax = LogSessionCountMax + LogGetterSessionCountMax;
|
||||
|
||||
constexpr inline size_t PortCountMax = 2;
|
||||
|
||||
struct ServerManagerOptions {
|
||||
static constexpr size_t PointerBufferSize = 0x400;
|
||||
static constexpr size_t MaxDomains = 31;
|
||||
static constexpr size_t MaxDomainObjects = 61;
|
||||
};
|
||||
|
||||
using ServerManager = sf::hipc::ServerManager<PortCountMax, ServerManagerOptions, SessionCountMax>;
|
||||
|
||||
constinit util::TypedStorage<ServerManager> g_server_manager_storage;
|
||||
constinit ServerManager *g_server_manager = nullptr;
|
||||
|
||||
constinit util::TypedStorage<psc::PmModule> g_pm_module_storage;
|
||||
constinit psc::PmModule *g_pm_module;
|
||||
constinit os::WaitableHolderType g_pm_module_holder;
|
||||
|
||||
constexpr const psc::PmModuleId PmModuleDependencies[] = { psc::PmModuleId_TmaHostIo, psc::PmModuleId_Fs };
|
||||
|
||||
/* Service objects. */
|
||||
constinit sf::UnmanagedServiceObject<lm::ILogService, lm::srv::LogServiceImpl> g_log_service_object;
|
||||
constinit sf::UnmanagedServiceObject<lm::ILogGetter, lm::srv::LogGetter> g_log_getter_service_object;
|
||||
|
||||
constinit std::atomic<bool> g_is_sleeping = false;
|
||||
|
||||
}
|
||||
|
||||
bool IsSleeping() {
|
||||
return g_is_sleeping;
|
||||
}
|
||||
|
||||
void InitializeIpcServer() {
|
||||
/* Check that we're not already initialized. */
|
||||
AMS_ABORT_UNLESS(g_server_manager == nullptr);
|
||||
AMS_ABORT_UNLESS(g_pm_module == nullptr);
|
||||
|
||||
/* Create and initialize the psc module. */
|
||||
g_pm_module = util::ConstructAt(g_pm_module_storage);
|
||||
R_ABORT_UNLESS(g_pm_module->Initialize(psc::PmModuleId_Lm, PmModuleDependencies, util::size(PmModuleDependencies), os::EventClearMode_ManualClear));
|
||||
|
||||
/* Create the psc module waitable holder. */
|
||||
os::InitializeWaitableHolder(std::addressof(g_pm_module_holder), g_pm_module->GetEventPointer()->GetBase());
|
||||
os::SetWaitableHolderUserData(std::addressof(g_pm_module_holder), psc::PmModuleId_Lm);
|
||||
|
||||
/* Create the server manager. */
|
||||
g_server_manager = util::ConstructAt(g_server_manager_storage);
|
||||
|
||||
/* Add the pm module holder. */
|
||||
g_server_manager->AddUserWaitableHolder(std::addressof(g_pm_module_holder));
|
||||
|
||||
/* Create services. */
|
||||
R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_service_object.GetShared(), LogServiceName, LogSessionCountMax));
|
||||
R_ABORT_UNLESS(g_server_manager->RegisterObjectForServer(g_log_getter_service_object.GetShared(), LogGetterServiceName, LogGetterSessionCountMax));
|
||||
|
||||
/* Start the server manager. */
|
||||
g_server_manager->ResumeProcessing();
|
||||
}
|
||||
|
||||
void LoopIpcServer() {
|
||||
/* Check that we're initialized. */
|
||||
AMS_ABORT_UNLESS(g_server_manager != nullptr);
|
||||
|
||||
/* Loop forever, servicing the server. */
|
||||
auto prev_state = psc::PmState_Unknown;
|
||||
while (true) {
|
||||
/* Get the next signaled holder. */
|
||||
auto *signaled_holder = g_server_manager->WaitSignaled();
|
||||
if (signaled_holder != std::addressof(g_pm_module_holder)) {
|
||||
/* If ipc, process. */
|
||||
R_ABORT_UNLESS(g_server_manager->Process(signaled_holder));
|
||||
} else {
|
||||
/* If pm module, clear the event. */
|
||||
g_pm_module->GetEventPointer()->Clear();
|
||||
g_server_manager->AddUserWaitableHolder(signaled_holder);
|
||||
|
||||
/* Get the power state. */
|
||||
psc::PmState pm_state;
|
||||
psc::PmFlagSet pm_flags;
|
||||
R_ABORT_UNLESS(g_pm_module->GetRequest(std::addressof(pm_state), std::addressof(pm_flags)));
|
||||
|
||||
/* Handle the power state. */
|
||||
if (prev_state == psc::PmState_EssentialServicesAwake && pm_state == psc::PmState_MinimumAwake) {
|
||||
g_is_sleeping = false;
|
||||
} else if (prev_state == psc::PmState_MinimumAwake && pm_state == psc::PmState_SleepReady) {
|
||||
g_is_sleeping = true;
|
||||
} else if (pm_state == psc::PmState_ShutdownReady) {
|
||||
g_is_sleeping = true;
|
||||
}
|
||||
|
||||
/* Set the previous state. */
|
||||
prev_state = pm_state;
|
||||
|
||||
/* Acknowledge the state transition. */
|
||||
R_ABORT_UNLESS(g_pm_module->Acknowledge(pm_state, ResultSuccess()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StopIpcServer() {
|
||||
/* Check that we're initialized. */
|
||||
AMS_ABORT_UNLESS(g_server_manager != nullptr);
|
||||
|
||||
/* Stop the server manager. */
|
||||
g_server_manager->RequestStopProcessing();
|
||||
}
|
||||
|
||||
void FinalizeIpcServer() {
|
||||
/* Check that we're initialized. */
|
||||
AMS_ABORT_UNLESS(g_server_manager != nullptr);
|
||||
|
||||
/* Destroy the server manager. */
|
||||
std::destroy_at(g_server_manager);
|
||||
g_server_manager = nullptr;
|
||||
}
|
||||
|
||||
}
|
207
libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp
Normal file
207
libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_buffer.hpp"
|
||||
#include "lm_log_server_proxy.hpp"
|
||||
#include "lm_sd_card_logger.hpp"
|
||||
#include "lm_time_util.hpp"
|
||||
#include "lm_log_packet_parser.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
void UpdateUserSystemClock(const u8 *data, size_t size) {
|
||||
/* Get the current time. */
|
||||
const time::PosixTime current_time = GetCurrentTime();
|
||||
|
||||
/* Get the base time. */
|
||||
s64 base_time = current_time.value - os::GetSystemTick().ToTimeSpan().GetSeconds();
|
||||
|
||||
/* Modify the message timestamp. */
|
||||
LogPacketParser::ParsePacket(data, size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool {
|
||||
/* Check that we're a header message. */
|
||||
if (!header.IsHead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Find the timestamp data chunk. */
|
||||
return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg) -> bool {
|
||||
/* Convert the argument. */
|
||||
const s64 *p_base_time = static_cast<const s64 *>(arg);
|
||||
|
||||
/* Modify user system clock. */
|
||||
if (key == impl::LogDataChunkKey_UserSystemClock) {
|
||||
/* Get the time from the chunk. */
|
||||
s64 time;
|
||||
AMS_ASSERT(chunk_size == sizeof(time));
|
||||
std::memcpy(std::addressof(time), chunk, chunk_size);
|
||||
|
||||
/* Add the base time. */
|
||||
time += *p_base_time;
|
||||
|
||||
/* Update the time in the chunk. */
|
||||
std::memcpy(const_cast<void *>(chunk), std::addressof(time), sizeof(time));
|
||||
}
|
||||
|
||||
return true;
|
||||
}, arg);
|
||||
}, std::addressof(base_time));
|
||||
}
|
||||
|
||||
bool DefaultFlushFunction(const u8 *data, size_t size) {
|
||||
/* Update clock. */
|
||||
static constinit bool s_is_user_system_clock_updated = false;
|
||||
if (!s_is_user_system_clock_updated) {
|
||||
UpdateUserSystemClock(data, size);
|
||||
s_is_user_system_clock_updated = true;
|
||||
}
|
||||
|
||||
/* Send the message. */
|
||||
const bool tma_success = LogServerProxy::GetInstance().Send(data, size);
|
||||
const bool sd_success = SdCardLogger::GetInstance().Write(data, size);
|
||||
const bool is_success = tma_success || sd_success;
|
||||
|
||||
/* If we succeeded, wipe the current time. */
|
||||
s_is_user_system_clock_updated &= !is_success;
|
||||
|
||||
return is_success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogBuffer &LogBuffer::GetDefaultInstance() {
|
||||
static constinit u8 s_default_buffers[128_KB * 2];
|
||||
static constinit LogBuffer s_default_log_buffer(s_default_buffers, sizeof(s_default_buffers), DefaultFlushFunction);
|
||||
|
||||
return s_default_log_buffer;
|
||||
}
|
||||
|
||||
void LogBuffer::CancelPush() {
|
||||
/* Acquire exclusive access to the push buffer. */
|
||||
std::scoped_lock lk(m_push_buffer_mutex);
|
||||
|
||||
/* Cancel any pending pushes. */
|
||||
if (m_push_ready_wait_count > 0) {
|
||||
m_push_canceled = true;
|
||||
m_cv_push_ready.Broadcast();
|
||||
}
|
||||
}
|
||||
|
||||
bool LogBuffer::PushImpl(const void *data, size_t size, bool blocking) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(size <= m_buffer_size);
|
||||
AMS_ASSERT(data != nullptr || size == 0);
|
||||
|
||||
/* Check that we have data to push. */
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Wait to be able to push. */
|
||||
u8 *dst;
|
||||
{
|
||||
/* Acquire exclusive access to the push buffer. */
|
||||
std::scoped_lock lk(m_push_buffer_mutex);
|
||||
|
||||
/* Wait for enough space to be available. */
|
||||
while (size > m_buffer_size - m_push_buffer->m_stored_size) {
|
||||
/* Only block if we're allowed to. */
|
||||
if (!blocking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Wait for push to be ready. */
|
||||
{
|
||||
++m_push_ready_wait_count;
|
||||
m_cv_push_ready.Wait(m_push_buffer_mutex);
|
||||
--m_push_ready_wait_count;
|
||||
}
|
||||
|
||||
/* Check if push was canceled. */
|
||||
if (m_push_canceled) {
|
||||
if (m_push_ready_wait_count == 0) {
|
||||
m_push_canceled = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the destination. */
|
||||
dst = m_push_buffer->m_head + m_push_buffer->m_stored_size;
|
||||
|
||||
/* Advance the push buffer. */
|
||||
m_push_buffer->m_stored_size += size;
|
||||
++m_push_buffer->m_reference_count;
|
||||
}
|
||||
|
||||
/* Copy the data to the push buffer. */
|
||||
std::memcpy(dst, data, size);
|
||||
|
||||
/* Close our push buffer reference, and signal that we can flush. */
|
||||
{
|
||||
/* Acquire exclusive access to the push buffer. */
|
||||
std::scoped_lock lk(m_push_buffer_mutex);
|
||||
|
||||
/* If there are no pending pushes, signal that we can flush. */
|
||||
if ((--m_push_buffer->m_reference_count) == 0) {
|
||||
m_cv_flush_ready.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogBuffer::FlushImpl(bool blocking) {
|
||||
/* Acquire exclusive access to the flush buffer. */
|
||||
std::scoped_lock lk(m_flush_buffer_mutex);
|
||||
|
||||
/* If we don't have data to flush, wait for us to have data. */
|
||||
if (m_flush_buffer->m_stored_size == 0) {
|
||||
/* Acquire exclusive access to the push buffer. */
|
||||
std::scoped_lock lk(m_push_buffer_mutex);
|
||||
|
||||
/* Wait for there to be pushed data. */
|
||||
while (m_push_buffer->m_stored_size == 0 || m_push_buffer->m_reference_count != 0) {
|
||||
/* Only block if we're allowed to. */
|
||||
if (!blocking) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Wait for us to be ready to flush. */
|
||||
m_cv_flush_ready.Wait(m_push_buffer_mutex);
|
||||
}
|
||||
|
||||
/* Swap the push buffer and the flush buffer pointers. */
|
||||
std::swap(m_push_buffer, m_flush_buffer);
|
||||
|
||||
/* Signal that we can push. */
|
||||
m_cv_push_ready.Broadcast();
|
||||
}
|
||||
|
||||
/* Flush any data. */
|
||||
if (!m_flush_function(m_flush_buffer->m_head, m_flush_buffer->m_stored_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Reset the flush buffer. */
|
||||
m_flush_buffer->m_stored_size = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
73
libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp
Normal file
73
libraries/libstratosphere/source/lm/srv/lm_log_buffer.hpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogBuffer {
|
||||
NON_COPYABLE(LogBuffer);
|
||||
NON_MOVEABLE(LogBuffer);
|
||||
private:
|
||||
struct BufferInfo {
|
||||
u8 *m_head;
|
||||
size_t m_stored_size;
|
||||
size_t m_reference_count;
|
||||
};
|
||||
public:
|
||||
using FlushFunction = bool (*)(const u8 *data, size_t size);
|
||||
private:
|
||||
BufferInfo m_buffers[2];
|
||||
BufferInfo *m_push_buffer;
|
||||
BufferInfo *m_flush_buffer;
|
||||
size_t m_buffer_size;
|
||||
FlushFunction m_flush_function;
|
||||
os::SdkMutex m_push_buffer_mutex;
|
||||
os::SdkMutex m_flush_buffer_mutex;
|
||||
os::SdkConditionVariable m_cv_push_ready;
|
||||
os::SdkConditionVariable m_cv_flush_ready;
|
||||
bool m_push_canceled;
|
||||
size_t m_push_ready_wait_count;
|
||||
public:
|
||||
constexpr explicit LogBuffer(void *buffer, size_t buffer_size, FlushFunction f)
|
||||
: m_buffers{}, m_push_buffer(m_buffers + 0), m_flush_buffer(m_buffers + 1),
|
||||
m_buffer_size(buffer_size / 2), m_flush_function(f), m_push_buffer_mutex{},
|
||||
m_flush_buffer_mutex{}, m_cv_push_ready{}, m_cv_flush_ready{},
|
||||
m_push_canceled(false), m_push_ready_wait_count(0)
|
||||
{
|
||||
AMS_ASSERT(buffer != nullptr);
|
||||
AMS_ASSERT(buffer_size > 0);
|
||||
AMS_ASSERT(f != nullptr);
|
||||
|
||||
m_buffers[0].m_head = static_cast<u8 *>(buffer);
|
||||
m_buffers[1].m_head = static_cast<u8 *>(buffer) + (buffer_size / 2);
|
||||
}
|
||||
|
||||
static LogBuffer &GetDefaultInstance();
|
||||
|
||||
bool Push(const void *data, size_t size) { return this->PushImpl(data, size, true); }
|
||||
bool TryPush(const void *data, size_t size) { return this->PushImpl(data, size, false); }
|
||||
|
||||
void CancelPush();
|
||||
|
||||
bool Flush() { return this->FlushImpl(true); }
|
||||
bool TryFlush() { return this->FlushImpl(false); }
|
||||
private:
|
||||
bool PushImpl(const void *data, size_t size, bool blocking);
|
||||
bool FlushImpl(bool blocking);
|
||||
};
|
||||
|
||||
}
|
46
libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp
Normal file
46
libraries/libstratosphere/source/lm/srv/lm_log_getter.cpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_getter.hpp"
|
||||
#include "lm_log_getter_impl.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
extern bool g_is_logging_to_custom_sink;
|
||||
|
||||
Result LogGetter::StartLogging() {
|
||||
g_is_logging_to_custom_sink = true;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LogGetter::StopLogging() {
|
||||
g_is_logging_to_custom_sink = false;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LogGetter::GetLog(const sf::OutAutoSelectBuffer &message, sf::Out<s64> out_size, sf::Out<u32> out_drop_count) {
|
||||
/* Try to flush logs. */
|
||||
if (LogGetterImpl::GetBuffer().TryFlush()) {
|
||||
*out_size = LogGetterImpl::GetLog(message.GetPointer(), message.GetSize(), out_drop_count.GetPointer());
|
||||
} else {
|
||||
/* Otherwise, we got no data. */
|
||||
*out_size = 0;
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
30
libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp
Normal file
30
libraries/libstratosphere/source/lm/srv/lm_log_getter.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "../sf/lm_i_log_getter.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogGetter {
|
||||
public:
|
||||
Result StartLogging();
|
||||
Result StopLogging();
|
||||
Result GetLog(const sf::OutAutoSelectBuffer &message, sf::Out<s64> out_size, sf::Out<u32> out_drop_count);
|
||||
};
|
||||
static_assert(lm::IsILogGetter<LogGetter>);
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_getter_impl.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
CustomSinkBuffer &LogGetterImpl::GetBuffer() {
|
||||
static constinit u8 s_buffer[32_KB];
|
||||
static constinit CustomSinkBuffer s_custom_sink_buffer(s_buffer, sizeof(s_buffer), FlushFunction);
|
||||
return s_custom_sink_buffer;
|
||||
}
|
||||
|
||||
s64 LogGetterImpl::GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count) {
|
||||
/* Check pre-condition. */
|
||||
AMS_ASSERT(buffer != nullptr);
|
||||
|
||||
/* Determine how much we can get. */
|
||||
size_t min_size = s_buffer_size;
|
||||
if (buffer_size < s_buffer_size) {
|
||||
min_size = buffer_size;
|
||||
IncreaseLogPacketDropCount();
|
||||
}
|
||||
|
||||
/* Get the data. */
|
||||
std::memcpy(buffer, s_message, min_size);
|
||||
|
||||
/* Set output drop count. */
|
||||
*out_drop_count = s_log_packet_drop_count;
|
||||
s_log_packet_drop_count = 0;
|
||||
|
||||
return min_size;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_custom_sink_buffer.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogGetterImpl {
|
||||
NON_COPYABLE(LogGetterImpl);
|
||||
NON_MOVEABLE(LogGetterImpl);
|
||||
private:
|
||||
static constinit inline const u8 *s_message = nullptr;
|
||||
static constinit inline size_t s_buffer_size = 0;
|
||||
static constinit inline size_t s_log_packet_drop_count = 0;
|
||||
private:
|
||||
LogGetterImpl();
|
||||
public:
|
||||
static CustomSinkBuffer &GetBuffer();
|
||||
static s64 GetLog(void *buffer, size_t buffer_size, u32 *out_drop_count);
|
||||
|
||||
static void IncreaseLogPacketDropCount() { ++s_log_packet_drop_count; }
|
||||
private:
|
||||
static bool FlushFunction(const u8 *buffer, size_t buffer_size) {
|
||||
s_message = buffer;
|
||||
s_buffer_size = buffer_size;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
276
libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp
Normal file
276
libraries/libstratosphere/source/lm/srv/lm_log_packet_parser.cpp
Normal file
|
@ -0,0 +1,276 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_packet_parser.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
const u8 *ParseUleb128(u64 *out, const u8 *cur, const u8 *end) {
|
||||
u64 value = 0;
|
||||
size_t shift = 0;
|
||||
while (cur < end && shift + 7 <= BITSIZEOF(u64)) {
|
||||
value |= static_cast<u64>(*cur & 0x7F) << shift;
|
||||
if ((*cur & 0x80) == 0) {
|
||||
*out = value;
|
||||
return cur;
|
||||
}
|
||||
|
||||
++cur;
|
||||
shift += 7;
|
||||
}
|
||||
|
||||
return end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool LogPacketParser::ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg) {
|
||||
const u8 *cur = static_cast<const u8 *>(buffer);
|
||||
const u8 *end = cur + buffer_size;
|
||||
|
||||
while (cur < end) {
|
||||
/* Check that we can parse a header. */
|
||||
size_t remaining_size = end - cur;
|
||||
if (remaining_size < sizeof(impl::LogPacketHeader)) {
|
||||
AMS_ASSERT(remaining_size >= sizeof(impl::LogPacketHeader));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get the header. */
|
||||
impl::LogPacketHeader header;
|
||||
std::memcpy(std::addressof(header), cur, sizeof(header));
|
||||
|
||||
/* Advance past the header. */
|
||||
cur += sizeof(header);
|
||||
|
||||
/* Check that we can parse the payload. */
|
||||
const auto payload_size = header.GetPayloadSize();
|
||||
remaining_size = end - cur;
|
||||
if (remaining_size < payload_size) {
|
||||
AMS_ASSERT(remaining_size >= payload_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Invoke the callback. */
|
||||
if (!callback(header, cur, payload_size, arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
cur += payload_size;
|
||||
}
|
||||
|
||||
/* Check that we parsed all the data. */
|
||||
AMS_ASSERT(cur == end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogPacketParser::ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg) {
|
||||
const u8 *cur = static_cast<const u8 *>(payload);
|
||||
const u8 *end = cur + payload_size;
|
||||
|
||||
while (cur < end) {
|
||||
/* Get the key. */
|
||||
u64 key;
|
||||
const auto key_last = ParseUleb128(std::addressof(key), cur, end);
|
||||
if (key_last >= end) {
|
||||
return false;
|
||||
}
|
||||
cur = key_last + 1;
|
||||
|
||||
/* Get the size. */
|
||||
u64 size;
|
||||
const auto size_last = ParseUleb128(std::addressof(size), cur, end);
|
||||
if (size_last >= end) {
|
||||
return false;
|
||||
}
|
||||
cur = size_last + 1;
|
||||
|
||||
/* If we're in bounds, invoke the callback. */
|
||||
if (cur + size <= end && !callback(static_cast<impl::LogDataChunkKey>(key), cur, size, arg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cur += size;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LogPacketParser::FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size) {
|
||||
/* Create context for iteration. */
|
||||
struct FindDataChunkContext {
|
||||
const void *chunk;
|
||||
size_t chunk_size;
|
||||
bool found;
|
||||
impl::LogDataChunkKey key;
|
||||
} context = { nullptr, 0, false, key };
|
||||
|
||||
/* Find the chunk. */
|
||||
LogPacketParser::ParsePacket(buffer, buffer_size, [](const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg) -> bool {
|
||||
/* If the header isn't a header packet, continue. */
|
||||
if (!header.IsHead()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return LogPacketParser::ParseDataChunk(payload, payload_size, [](impl::LogDataChunkKey cur_key, const void *chunk, size_t chunk_size, void *arg) -> bool {
|
||||
/* Get the context. */
|
||||
auto *context = static_cast<FindDataChunkContext *>(arg);
|
||||
|
||||
/* Check if we found the desired key. */
|
||||
if (context->key == cur_key) {
|
||||
context->chunk = chunk;
|
||||
context->chunk_size = chunk_size;
|
||||
context->found = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Otherwise, continue. */
|
||||
return true;
|
||||
}, arg);
|
||||
}, std::addressof(context));
|
||||
|
||||
/* Write the chunk we found. */
|
||||
if (context.found) {
|
||||
*out = context.chunk;
|
||||
*out_size = context.chunk_size;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t LogPacketParser::ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
AMS_ASSERT(dst_size > 0);
|
||||
AMS_ASSERT(buffer != nullptr);
|
||||
AMS_ASSERT(buffer_size > 0);
|
||||
|
||||
/* Find the relevant data chunk. */
|
||||
const void *chunk;
|
||||
size_t chunk_size;
|
||||
const bool found = LogPacketParser::FindDataChunk(std::addressof(chunk), std::addressof(chunk_size), impl::LogDataChunkKey_ModuleName, buffer, buffer_size);
|
||||
if (!found || chunk_size == 0) {
|
||||
dst[0] = '\x00';
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copy as much of the module name as we can. */
|
||||
const size_t copy_size = std::min(chunk_size, dst_size - 1);
|
||||
std::memcpy(dst, chunk, copy_size);
|
||||
dst[copy_size] = '\x00';
|
||||
|
||||
return chunk_size;
|
||||
}
|
||||
|
||||
void LogPacketParser::ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg) {
|
||||
/* Declare context for inter-call storage. */
|
||||
struct PreviousPacketContext {
|
||||
u64 process_id;
|
||||
u64 thread_id;
|
||||
size_t carry_size;
|
||||
bool ends_with_text_log;
|
||||
};
|
||||
static constinit PreviousPacketContext s_previous_packet_context = {};
|
||||
|
||||
/* Get the packet header. */
|
||||
auto *header = static_cast<const impl::LogPacketHeader *>(buffer);
|
||||
auto *payload = static_cast<const char *>(buffer) + impl::LogPacketHeaderSize;
|
||||
auto payload_size = buffer_size - impl::LogPacketHeaderSize;
|
||||
|
||||
/* Determine if the packet is a continuation. */
|
||||
const bool is_continuation = !header->IsHead() && header->GetProcessId() == s_previous_packet_context.process_id && header->GetThreadId() == s_previous_packet_context.thread_id;
|
||||
|
||||
/* Require that the packet be a header or a continuation. */
|
||||
if (!header->IsHead() && !is_continuation) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If the packet is a continuation, handle the leftover data. */
|
||||
if (is_continuation && s_previous_packet_context.carry_size > 0) {
|
||||
/* Invoke the callback on what we can. */
|
||||
const size_t sendable = std::min(s_previous_packet_context.carry_size, payload_size);
|
||||
if (s_previous_packet_context.ends_with_text_log) {
|
||||
callback(payload, sendable, arg);
|
||||
}
|
||||
|
||||
/* Advance the leftover data. */
|
||||
s_previous_packet_context.carry_size -= sendable;
|
||||
payload += sendable;
|
||||
payload_size -= sendable;
|
||||
}
|
||||
|
||||
/* If we've sent the whole payload, we're done. */
|
||||
if (payload_size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Parse the payload. */
|
||||
size_t carry_size = 0;
|
||||
bool ends_with_text_log = false;
|
||||
{
|
||||
const u8 *cur = reinterpret_cast<const u8 *>(payload);
|
||||
const u8 *end = cur + payload_size;
|
||||
|
||||
while (cur < end) {
|
||||
/* Get the key. */
|
||||
u64 key;
|
||||
const auto key_last = ParseUleb128(std::addressof(key), cur, end);
|
||||
if (key_last >= end) {
|
||||
break;
|
||||
}
|
||||
cur = key_last + 1;
|
||||
|
||||
/* Get the size. */
|
||||
u64 size;
|
||||
const auto size_last = ParseUleb128(std::addressof(size), cur, end);
|
||||
if (size_last >= end) {
|
||||
break;
|
||||
}
|
||||
cur = size_last + 1;
|
||||
|
||||
/* Process the data. */
|
||||
const bool is_text_log = static_cast<impl::LogDataChunkKey>(key) == impl::LogDataChunkKey_TextLog;
|
||||
const size_t remaining = end - cur;
|
||||
|
||||
if (size >= remaining) {
|
||||
carry_size = size - remaining;
|
||||
ends_with_text_log = is_text_log;
|
||||
}
|
||||
|
||||
if (is_text_log) {
|
||||
const size_t sendable_size = std::min(size, remaining);
|
||||
callback(reinterpret_cast<const char *>(cur), sendable_size, arg);
|
||||
}
|
||||
|
||||
cur += size;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the packet isn't a tail packet, update the context. */
|
||||
if (!header->IsTail()) {
|
||||
s_previous_packet_context.process_id = header->GetProcessId();
|
||||
s_previous_packet_context.thread_id = header->GetThreadId();
|
||||
s_previous_packet_context.carry_size = carry_size;
|
||||
s_previous_packet_context.ends_with_text_log = ends_with_text_log;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "../impl/lm_log_data_chunk.hpp"
|
||||
#include "../impl/lm_log_packet_header.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogPacketParser {
|
||||
public:
|
||||
using ParsePacketCallback = bool (*)(const impl::LogPacketHeader &header, const void *payload, size_t payload_size, void *arg);
|
||||
using ParseDataChunkCallback = bool (*)(impl::LogDataChunkKey key, const void *chunk, size_t chunk_size, void *arg);
|
||||
using ParseTextLogCallback = void (*)(const char *txt, size_t size, void *arg);
|
||||
public:
|
||||
static bool ParsePacket(const void *buffer, size_t buffer_size, ParsePacketCallback callback, void *arg);
|
||||
static bool ParseDataChunk(const void *payload, size_t payload_size, ParseDataChunkCallback callback, void *arg);
|
||||
|
||||
static bool FindDataChunk(const void **out, size_t *out_size, impl::LogDataChunkKey key, const void *buffer, size_t buffer_size);
|
||||
|
||||
static size_t ParseModuleName(char *dst, size_t dst_size, const void *buffer, size_t buffer_size);
|
||||
|
||||
static void ParseTextLogWithContext(const void *buffer, size_t buffer_size, ParseTextLogCallback callback, void *arg);
|
||||
};
|
||||
|
||||
}
|
218
libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp
Normal file
218
libraries/libstratosphere/source/lm/srv/lm_log_server_proxy.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_server_proxy.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline const char PortName[] = "iywys@$LogManager";
|
||||
constexpr inline const int HtcsSessionCountMax = 2;
|
||||
constexpr inline const TimeSpan PollingInterval = TimeSpan::FromSeconds(1);
|
||||
|
||||
constinit u8 g_htcs_heap_buffer[2_KB];
|
||||
|
||||
constexpr inline const int InvalidHtcsSocket = -1;
|
||||
|
||||
constexpr ALWAYS_INLINE bool IsValidHtcsSocket(int socket) {
|
||||
return socket >= 0;
|
||||
}
|
||||
|
||||
static_assert(!IsValidHtcsSocket(InvalidHtcsSocket));
|
||||
|
||||
bool IsHtcEnabled() {
|
||||
u8 enable_htc = 0;
|
||||
settings::fwdbg::GetSettingsItemValue(&enable_htc, sizeof(enable_htc), "atmosphere", "enable_htc");
|
||||
return enable_htc != 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LogServerProxy::LogServerProxy() : m_cv_connected(), m_stop_event(os::EventClearMode_ManualClear), m_connection_mutex(), m_observer_mutex(), m_server_socket(InvalidHtcsSocket), m_client_socket(InvalidHtcsSocket), m_connection_observer(nullptr) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void LogServerProxy::Start() {
|
||||
/* Create thread. */
|
||||
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), [](void *_this) { static_cast<LogServerProxy *>(_this)->LoopAuto(); }, this, m_thread_stack, sizeof(m_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(lm, HtcsConnection)));
|
||||
|
||||
/* Set thread name pointer. */
|
||||
os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(lm, HtcsConnection));
|
||||
|
||||
/* Clear stop event. */
|
||||
m_stop_event.Clear();
|
||||
|
||||
/* Start thread. */
|
||||
os::StartThread(std::addressof(m_thread));
|
||||
}
|
||||
|
||||
void LogServerProxy::Stop() {
|
||||
/* Signal to connection thread to stop. */
|
||||
m_stop_event.Signal();
|
||||
|
||||
/* Close client socket. */
|
||||
if (const int client_socket = m_client_socket; client_socket >= 0) {
|
||||
htcs::Close(client_socket);
|
||||
}
|
||||
|
||||
/* Close server socket. */
|
||||
if (const int server_socket = m_server_socket; server_socket >= 0) {
|
||||
htcs::Close(server_socket);
|
||||
}
|
||||
|
||||
/* Wait for the connection thread to exit. */
|
||||
os::WaitThread(std::addressof(m_thread));
|
||||
os::DestroyThread(std::addressof(m_thread));
|
||||
}
|
||||
|
||||
bool LogServerProxy::IsConnected() {
|
||||
/* Return whether there's a valid client socket. */
|
||||
return IsValidHtcsSocket(m_client_socket);
|
||||
}
|
||||
|
||||
void LogServerProxy::SetConnectionObserver(ConnectionObserver observer) {
|
||||
/* Acquire exclusive access to observer data. */
|
||||
std::scoped_lock lk(m_observer_mutex);
|
||||
|
||||
/* Set the observer. */
|
||||
m_connection_observer = observer;
|
||||
}
|
||||
|
||||
bool LogServerProxy::Send(const u8 *data, size_t size) {
|
||||
/* Send as much data as we can, until it's all send. */
|
||||
size_t offset = 0;
|
||||
while (this->IsConnected() && offset < size) {
|
||||
/* Try to send the remaining data. */
|
||||
if (const auto result = htcs::Send(m_client_socket, data + offset, size - offset, 0); result >= 0) {
|
||||
/* Advance. */
|
||||
offset += static_cast<size_t>(result);
|
||||
} else {
|
||||
/* We failed to send data, shutdown the socket. */
|
||||
htcs::Shutdown(m_client_socket, htcs::HTCS_SHUT_RDWR);
|
||||
|
||||
/* Wait a second, before returning to the caller. */
|
||||
os::SleepThread(TimeSpan::FromSeconds(1));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return whether we sent all the data. */
|
||||
AMS_ASSERT(offset <= size);
|
||||
return offset == size;
|
||||
}
|
||||
|
||||
void LogServerProxy::LoopAuto() {
|
||||
/* If we're not using htcs, there's nothing to do. */
|
||||
if (!IsHtcEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Check that we have enough working memory. */
|
||||
const auto working_memory_size = htcs::GetWorkingMemorySize(HtcsSessionCountMax);
|
||||
AMS_ABORT_UNLESS(working_memory_size <= sizeof(g_htcs_heap_buffer));
|
||||
|
||||
/* Initialize htcs for the duration that we loop. */
|
||||
htcs::InitializeForDisableDisconnectionEmulation(g_htcs_heap_buffer, working_memory_size);
|
||||
ON_SCOPE_EXIT { htcs::Finalize(); };
|
||||
|
||||
/* Setup socket address. */
|
||||
htcs::SockAddrHtcs server_address;
|
||||
server_address.family = htcs::HTCS_AF_HTCS;
|
||||
server_address.peer_name = htcs::GetPeerNameAny();
|
||||
std::strcpy(server_address.port_name.name, PortName);
|
||||
|
||||
/* Manage htcs connections until a stop is requested. */
|
||||
do {
|
||||
/* Create a server socket. */
|
||||
const auto server_socket = htcs::Socket();
|
||||
if (!IsValidHtcsSocket(server_socket)) {
|
||||
continue;
|
||||
}
|
||||
m_server_socket = server_socket;
|
||||
|
||||
/* Ensure we cleanup the server socket when done. */
|
||||
ON_SCOPE_EXIT {
|
||||
htcs::Close(server_socket);
|
||||
m_server_socket = InvalidHtcsSocket;
|
||||
};
|
||||
|
||||
/* Bind to the server socket. */
|
||||
if (htcs::Bind(server_socket, std::addressof(server_address)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Listen on the server socket. */
|
||||
if (htcs::Listen(server_socket, 0) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Loop on clients, until we're asked to stop. */
|
||||
while (!m_stop_event.TryWait()) {
|
||||
/* Accept a client socket. */
|
||||
const auto client_socket = htcs::Accept(server_socket, nullptr);
|
||||
if (!IsValidHtcsSocket(client_socket)) {
|
||||
break;
|
||||
}
|
||||
m_client_socket = client_socket;
|
||||
|
||||
/* Note that we're connected. */
|
||||
this->InvokeConnectionObserver(true);
|
||||
this->SignalConnection();
|
||||
|
||||
/* Ensure we cleanup the client socket when done. */
|
||||
ON_SCOPE_EXIT {
|
||||
htcs::Close(client_socket);
|
||||
m_client_socket = InvalidHtcsSocket;
|
||||
|
||||
this->InvokeConnectionObserver(false);
|
||||
};
|
||||
|
||||
/* Receive data (and do nothing with it), so long as we're connected. */
|
||||
u8 v;
|
||||
while (htcs::Recv(client_socket, std::addressof(v), sizeof(v), 0) == sizeof(v)) { /* ... */ }
|
||||
}
|
||||
} while (!m_stop_event.TimedWait(PollingInterval));
|
||||
}
|
||||
|
||||
void LogServerProxy::SignalConnection() {
|
||||
/* Acquire exclusive access to observer data. */
|
||||
std::scoped_lock lk(m_connection_mutex);
|
||||
|
||||
/* Broadcast to our connected cv. */
|
||||
m_cv_connected.Broadcast();
|
||||
}
|
||||
|
||||
void LogServerProxy::InvokeConnectionObserver(bool connected) {
|
||||
/* Acquire exclusive access to observer data. */
|
||||
std::scoped_lock lk(m_observer_mutex);
|
||||
|
||||
/* If we have an observer, observe the connection state. */
|
||||
if (m_connection_observer) {
|
||||
m_connection_observer(connected);
|
||||
}
|
||||
}
|
||||
|
||||
void StartLogServerProxy() {
|
||||
LogServerProxy::GetInstance().Start();
|
||||
}
|
||||
|
||||
void StopLogServerProxy() {
|
||||
LogServerProxy::GetInstance().Stop();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogServerProxy {
|
||||
AMS_SINGLETON_TRAITS(LogServerProxy);
|
||||
public:
|
||||
using ConnectionObserver = void (*)(bool connected);
|
||||
private:
|
||||
alignas(os::ThreadStackAlignment) u8 m_thread_stack[4_KB];
|
||||
os::ThreadType m_thread;
|
||||
os::SdkConditionVariable m_cv_connected;
|
||||
os::Event m_stop_event;
|
||||
os::SdkMutex m_connection_mutex;
|
||||
os::SdkMutex m_observer_mutex;
|
||||
std::atomic<int> m_server_socket;
|
||||
std::atomic<int> m_client_socket;
|
||||
ConnectionObserver m_connection_observer;
|
||||
public:
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
bool IsConnected();
|
||||
|
||||
void SetConnectionObserver(ConnectionObserver observer);
|
||||
|
||||
bool Send(const u8 *data, size_t size);
|
||||
private:
|
||||
void LoopAuto();
|
||||
void SignalConnection();
|
||||
void InvokeConnectionObserver(bool connected);
|
||||
};
|
||||
|
||||
void StartLogServerProxy();
|
||||
void StopLogServerProxy();
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_service_impl.hpp"
|
||||
#include "lm_logger_impl.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
struct LoggerImplAllocatorTag;
|
||||
using LoggerAllocator = ams::sf::ExpHeapStaticAllocator<3_KB, LoggerImplAllocatorTag>;
|
||||
using LoggerObjectFactory = ams::sf::ObjectFactory<typename LoggerAllocator::Policy>;
|
||||
|
||||
class StaticAllocatorInitializer {
|
||||
public:
|
||||
StaticAllocatorInitializer() {
|
||||
LoggerAllocator::Initialize(lmem::CreateOption_None);
|
||||
}
|
||||
} g_static_allocator_initializer;
|
||||
|
||||
}
|
||||
|
||||
Result LogServiceImpl::OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id) {
|
||||
/* Open logger. */
|
||||
out.SetValue(LoggerObjectFactory::CreateSharedEmplaced<::ams::lm::ILogger, LoggerImpl>(this, client_process_id.GetValue()));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "../sf/lm_i_log_service.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LogServiceImpl {
|
||||
public:
|
||||
Result OpenLogger(sf::Out<sf::SharedPointer<::ams::lm::ILogger>> out, const sf::ClientProcessId &client_process_id);
|
||||
};
|
||||
static_assert(lm::IsILogService<LogServiceImpl>);
|
||||
|
||||
}
|
147
libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp
Normal file
147
libraries/libstratosphere/source/lm/srv/lm_logger_impl.cpp
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_logger_impl.hpp"
|
||||
#include "lm_event_log_transmitter.hpp"
|
||||
#include "lm_log_buffer.hpp"
|
||||
#include "lm_log_packet_parser.hpp"
|
||||
#include "lm_log_getter_impl.hpp"
|
||||
#include "../impl/lm_log_packet_header.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
bool IsFlushAvailable();
|
||||
|
||||
bool g_is_logging_to_custom_sink = false;
|
||||
|
||||
namespace {
|
||||
|
||||
constinit u32 g_log_destination = lm::LogDestination_TargetManager;
|
||||
|
||||
bool SetProcessId(const sf::InAutoSelectBuffer &message, u64 process_id) {
|
||||
/* Check the message. */
|
||||
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(message.GetPointer()), alignof(impl::LogPacketHeader)));
|
||||
|
||||
/* Get a modifiable copy of the header. */
|
||||
auto *header = const_cast<impl::LogPacketHeader *>(reinterpret_cast<const impl::LogPacketHeader *>(message.GetPointer()));
|
||||
|
||||
/* Check that the message size is correct. */
|
||||
if (impl::LogPacketHeaderSize + header->GetPayloadSize() != message.GetSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set the header's process id. */
|
||||
header->SetProcessId(process_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PutLogToTargetManager(const sf::InAutoSelectBuffer &message) {
|
||||
/* Try to push the message. */
|
||||
bool success;
|
||||
if (IsFlushAvailable()) {
|
||||
success = LogBuffer::GetDefaultInstance().Push(message.GetPointer(), message.GetSize());
|
||||
} else {
|
||||
success = LogBuffer::GetDefaultInstance().TryPush(message.GetPointer(), message.GetSize());
|
||||
}
|
||||
|
||||
/* If we fail, increment dropped packet count. */
|
||||
if (!success) {
|
||||
EventLogTransmitter::GetDefaultInstance().IncreaseLogPacketDropCount();
|
||||
}
|
||||
}
|
||||
|
||||
void PutLogToUart(const sf::InAutoSelectBuffer &message) {
|
||||
#if defined(AMS_BUILD_FOR_DEBUGGING) || defined(AMS_BUILD_FOR_AUDITING)
|
||||
{
|
||||
/* Get header. */
|
||||
auto *data = message.GetPointer();
|
||||
auto data_size = message.GetSize();
|
||||
const auto *header = reinterpret_cast<const impl::LogPacketHeader *>(data);
|
||||
|
||||
/* Get the module name. */
|
||||
char module_name[0x10] = {};
|
||||
LogPacketParser::ParseModuleName(module_name, sizeof(module_name), data, data_size);
|
||||
|
||||
/* Create log metadata. */
|
||||
const diag::LogMetaData log_meta = {
|
||||
.module_name = module_name,
|
||||
.severity = static_cast<diag::LogSeverity>(header->GetSeverity()),
|
||||
.verbosity = header->GetVerbosity(),
|
||||
};
|
||||
|
||||
LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *arg) {
|
||||
/* Get metadata. */
|
||||
const auto &meta = *static_cast<const diag::LogMetaData *>(arg);
|
||||
|
||||
/* Put the message to uart. */
|
||||
diag::impl::PutImpl(meta, txt, size);
|
||||
}, const_cast<diag::LogMetaData *>(std::addressof(log_meta)));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PutLogToCustomSink(const sf::InAutoSelectBuffer &message) {
|
||||
LogPacketParser::ParseTextLogWithContext(message.GetPointer(), message.GetSize(), [](const char *txt, size_t size, void *) {
|
||||
/* Try to push the message. */
|
||||
if (!LogGetterImpl::GetBuffer().TryPush(txt, size)) {
|
||||
LogGetterImpl::IncreaseLogPacketDropCount();
|
||||
}
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LoggerImpl::LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id) : m_parent(parent), m_process_id(process_id.value) {
|
||||
/* Log start of session for process. */
|
||||
EventLogTransmitter::GetDefaultInstance().PushLogSessionBegin(m_process_id);
|
||||
}
|
||||
|
||||
LoggerImpl::~LoggerImpl() {
|
||||
/* Log end of session for process. */
|
||||
EventLogTransmitter::GetDefaultInstance().PushLogSessionEnd(m_process_id);
|
||||
}
|
||||
|
||||
Result LoggerImpl::Log(const sf::InAutoSelectBuffer &message) {
|
||||
/* Try to set the log process id. */
|
||||
/* NOTE: Nintendo succeeds here, for whatever purpose, so we will as well. */
|
||||
R_UNLESS(SetProcessId(message, m_process_id), ResultSuccess());
|
||||
|
||||
/* If we should, log to target manager. */
|
||||
if (g_log_destination & lm::LogDestination_TargetManager) {
|
||||
PutLogToTargetManager(message);
|
||||
}
|
||||
|
||||
/* If we should, log to uart. */
|
||||
if ((g_log_destination & lm::LogDestination_Uart) || (IsFlushAvailable() && (g_log_destination & lm::LogDestination_UartIfSleep))) {
|
||||
PutLogToUart(message);
|
||||
}
|
||||
|
||||
/* If we should, log to custom sink. */
|
||||
if (g_is_logging_to_custom_sink) {
|
||||
PutLogToCustomSink(message);
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result LoggerImpl::SetDestination(u32 destination) {
|
||||
/* Set the log destination. */
|
||||
g_log_destination = destination;
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
}
|
35
libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp
Normal file
35
libraries/libstratosphere/source/lm/srv/lm_logger_impl.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_log_service_impl.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class LoggerImpl {
|
||||
private:
|
||||
LogServiceImpl *m_parent;
|
||||
u64 m_process_id;
|
||||
public:
|
||||
explicit LoggerImpl(LogServiceImpl *parent, os::ProcessId process_id);
|
||||
~LoggerImpl();
|
||||
public:
|
||||
Result Log(const sf::InAutoSelectBuffer &message);
|
||||
Result SetDestination(u32 destination);
|
||||
};
|
||||
static_assert(lm::IsILogger<LoggerImpl>);
|
||||
|
||||
}
|
361
libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp
Normal file
361
libraries/libstratosphere/source/lm/srv/lm_sd_card_logger.cpp
Normal file
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_sd_card_logger.hpp"
|
||||
#include "lm_time_util.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char SdCardMountName[] = "sdcard";
|
||||
constexpr const char LogFileExtension[] = "nxbinlog";
|
||||
|
||||
constexpr const char SettingName[] = "lm";
|
||||
constexpr const char SettingKeyLoggingEnabled[] = "enable_sd_card_logging";
|
||||
constexpr const char SettingKeyOutputDirectory[] = "sd_card_log_output_directory";
|
||||
|
||||
constexpr inline size_t LogFileHeaderSize = 8;
|
||||
constexpr inline u32 LogFileHeaderMagic = util::ReverseFourCC<'p','h','p','h'>::Code;
|
||||
constexpr inline u8 LogFileHeaderVersion = 1;
|
||||
|
||||
struct LogFileHeader {
|
||||
u32 magic;
|
||||
u8 version;
|
||||
u8 reserved[3];
|
||||
};
|
||||
static_assert(sizeof(LogFileHeader) == LogFileHeaderSize);
|
||||
|
||||
constinit os::SdkMutex g_sd_card_logging_enabled_mutex;
|
||||
constinit bool g_determined_sd_card_logging_enabled = false;
|
||||
constinit bool g_sd_card_logging_enabled = false;
|
||||
|
||||
constinit os::SdkMutex g_sd_card_detection_event_mutex;
|
||||
constinit bool g_sd_card_inserted_cache = false;
|
||||
constinit bool g_sd_card_detection_event_initialized = false;
|
||||
constinit std::unique_ptr<fs::IEventNotifier> g_sd_card_detection_event_notifier;
|
||||
os::SystemEvent g_sd_card_detection_event;
|
||||
|
||||
bool GetSdCardLoggingEnabledImpl() {
|
||||
bool enabled;
|
||||
const auto size = settings::fwdbg::GetSettingsItemValue(std::addressof(enabled), sizeof(enabled), SettingName, SettingKeyLoggingEnabled);
|
||||
if (size != sizeof(enabled)) {
|
||||
AMS_ASSERT(size == sizeof(enabled));
|
||||
return false;
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}
|
||||
|
||||
bool GetSdCardLoggingEnabled() {
|
||||
if (AMS_UNLIKELY(!g_determined_sd_card_logging_enabled)) {
|
||||
std::scoped_lock lk(g_sd_card_logging_enabled_mutex);
|
||||
|
||||
if (AMS_LIKELY(!g_determined_sd_card_logging_enabled)) {
|
||||
g_sd_card_logging_enabled = GetSdCardLoggingEnabledImpl();
|
||||
g_determined_sd_card_logging_enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
return g_sd_card_logging_enabled;
|
||||
}
|
||||
|
||||
void EnsureSdCardDetectionEventInitialized() {
|
||||
if (AMS_UNLIKELY(!g_sd_card_detection_event_initialized)) {
|
||||
std::scoped_lock lk(g_sd_card_detection_event_mutex);
|
||||
|
||||
if (AMS_LIKELY(!g_sd_card_detection_event_initialized)) {
|
||||
/* Create SD card detection event notifier. */
|
||||
R_ABORT_UNLESS(fs::OpenSdCardDetectionEventNotifier(std::addressof(g_sd_card_detection_event_notifier)));
|
||||
R_ABORT_UNLESS(g_sd_card_detection_event_notifier->BindEvent(g_sd_card_detection_event.GetBase(), os::EventClearMode_ManualClear));
|
||||
|
||||
/* Get initial inserted value. */
|
||||
g_sd_card_inserted_cache = fs::IsSdCardInserted();
|
||||
|
||||
g_sd_card_detection_event_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetSdCardStatus(bool *out_inserted, bool *out_status_changed) {
|
||||
/* Ensure that we can detect the sd card. */
|
||||
EnsureSdCardDetectionEventInitialized();
|
||||
|
||||
/* Check if there's a detection event. */
|
||||
const bool status_changed = g_sd_card_detection_event.TryWait();
|
||||
if (status_changed) {
|
||||
g_sd_card_detection_event.Clear();
|
||||
|
||||
/* Update the inserted cache. */
|
||||
g_sd_card_inserted_cache = fs::IsSdCardInserted();
|
||||
}
|
||||
|
||||
*out_inserted = g_sd_card_inserted_cache;
|
||||
*out_status_changed = status_changed;
|
||||
}
|
||||
|
||||
bool GetSdCardLogOutputDirectory(char *dst, size_t size) {
|
||||
/* Get the output directory size. */
|
||||
const auto value_size = settings::fwdbg::GetSettingsItemValueSize(SettingName, SettingKeyOutputDirectory);
|
||||
if (value_size > size) {
|
||||
AMS_ASSERT(value_size <= size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Get the output directory. */
|
||||
const auto read_size = settings::fwdbg::GetSettingsItemValue(dst, size, SettingName, SettingKeyOutputDirectory);
|
||||
AMS_ASSERT(read_size == value_size);
|
||||
|
||||
return read_size == value_size;
|
||||
}
|
||||
|
||||
bool EnsureLogDirectory(const char *dir) {
|
||||
/* Generate the log directory path. */
|
||||
char path[0x80];
|
||||
const size_t len = util::SNPrintf(path, sizeof(path), "%s:/%s", SdCardMountName, dir);
|
||||
if (len >= sizeof(path)) {
|
||||
AMS_ASSERT(len < sizeof(path));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure the directory. */
|
||||
/* NOTE: Nintendo does not perform recusrive directory ensure, only a single CreateDirectory level. */
|
||||
return R_SUCCEEDED(fs::EnsureDirectoryRecursively(path));
|
||||
}
|
||||
|
||||
bool MakeLogFilePathWithoutExtension(char *dst, size_t size, const char *dir) {
|
||||
/* Get the current time. */
|
||||
const auto cur_time = time::ToCalendarTimeInUtc(lm::srv::GetCurrentTime());
|
||||
|
||||
/* Get the device serial number. */
|
||||
settings::system::SerialNumber serial_number;
|
||||
settings::system::GetSerialNumber(std::addressof(serial_number));
|
||||
|
||||
/* Print the path. */
|
||||
const size_t len = util::SNPrintf(dst, size, "%s:/%s/%s_%04d%02d%02d%02d%02d%02d", SdCardMountName, dir, serial_number.str, cur_time.year, cur_time.month, cur_time.day, cur_time.hour, cur_time.minute, cur_time.second);
|
||||
|
||||
AMS_ASSERT(len < size);
|
||||
return len < size;
|
||||
}
|
||||
|
||||
bool GenerateLogFile(char *dst, size_t size, const char *dir) {
|
||||
/* Generate the log file path. */
|
||||
char path_without_ext[0x80];
|
||||
if (!MakeLogFilePathWithoutExtension(path_without_ext, sizeof(path_without_ext), dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to find an available log file path. */
|
||||
constexpr auto MaximumLogIndex = 99;
|
||||
for (auto i = 1; i <= MaximumLogIndex; ++i) {
|
||||
/* Print the current log file path. */
|
||||
const size_t len = (i == 1) ? util::SNPrintf(dst, size, "%s.%s", path_without_ext, LogFileExtension) : util::SNPrintf(dst, size, "%s_%d.%s", path_without_ext, i, LogFileExtension);
|
||||
if (len >= size) {
|
||||
AMS_ASSERT(len < size);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to create the log file. */
|
||||
const auto result = fs::CreateFile(dst, 0);
|
||||
if (R_SUCCEEDED(result)) {
|
||||
return true;
|
||||
} else if (fs::ResultPathAlreadyExists::Includes(result)) {
|
||||
/* The log file already exists, so try the next index. */
|
||||
continue;
|
||||
} else {
|
||||
/* We failed to create a log file. */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* We ran out of log file indices. */
|
||||
return false;
|
||||
}
|
||||
|
||||
Result WriteLogFileHeaderImpl(const char *path) {
|
||||
/* Open the log file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Write the log file header. */
|
||||
const LogFileHeader header = {
|
||||
.magic = LogFileHeaderMagic,
|
||||
.version = LogFileHeaderVersion
|
||||
};
|
||||
|
||||
R_TRY(fs::WriteFile(file, 0, std::addressof(header), sizeof(header), fs::WriteOption::Flush));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
bool WriteLogFileHeader(const char *path) {
|
||||
return R_SUCCEEDED(WriteLogFileHeaderImpl(path));
|
||||
}
|
||||
|
||||
Result WriteLogFileBodyImpl(const char *path, s64 offset, const u8 *data, size_t size) {
|
||||
/* Open the log file. */
|
||||
fs::FileHandle file;
|
||||
R_TRY(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Write | fs::OpenMode_AllowAppend));
|
||||
ON_SCOPE_EXIT { fs::CloseFile(file); };
|
||||
|
||||
/* Write the data. */
|
||||
R_TRY(fs::WriteFile(file, offset, data, size, fs::WriteOption::Flush));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
bool WriteLogFileBody(const char *path, s64 offset, const u8 *data, size_t size) {
|
||||
return R_SUCCEEDED(WriteLogFileBodyImpl(path, offset, data, size));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SdCardLogger::SdCardLogger() : m_logging_observer_mutex(), m_is_enabled(false), m_is_sd_card_mounted(false), m_is_sd_card_status_unknown(false), m_log_file_offset(0), m_logging_observer(nullptr) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
bool SdCardLogger::GetEnabled() const {
|
||||
return m_is_enabled;
|
||||
}
|
||||
|
||||
void SdCardLogger::SetEnabled(bool enabled) {
|
||||
/* Only update if we need to. */
|
||||
if (m_is_enabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set enabled. */
|
||||
m_is_enabled = enabled;
|
||||
|
||||
/* Invoke our observer. */
|
||||
std::scoped_lock lk(m_logging_observer_mutex);
|
||||
|
||||
if (m_logging_observer) {
|
||||
m_logging_observer(enabled);
|
||||
}
|
||||
}
|
||||
|
||||
void SdCardLogger::SetLoggingObserver(LoggingObserver observer) {
|
||||
std::scoped_lock lk(m_logging_observer_mutex);
|
||||
|
||||
m_logging_observer = observer;
|
||||
}
|
||||
|
||||
bool SdCardLogger::Initialize() {
|
||||
/* If we're already enabled, nothing to do. */
|
||||
if (this->GetEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Get the sd card status. */
|
||||
bool inserted = false, status_changed = false;
|
||||
GetSdCardStatus(std::addressof(inserted), std::addressof(status_changed));
|
||||
|
||||
/* Update whether status is known. */
|
||||
if (status_changed) {
|
||||
m_is_sd_card_status_unknown = false;
|
||||
}
|
||||
|
||||
/* If the SD isn't inserted, we can't initialize. */
|
||||
if (!inserted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the status is unknown, we can't initialize. */
|
||||
if (m_is_sd_card_status_unknown) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Mount the SD card. */
|
||||
if (R_FAILED(fs::MountSdCard(SdCardMountName))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Note that the SD card is mounted. */
|
||||
m_is_sd_card_mounted = true;
|
||||
|
||||
/* Get the output directory. */
|
||||
char output_dir[0x80];
|
||||
if (!GetSdCardLogOutputDirectory(output_dir, sizeof(output_dir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure the output directory exists. */
|
||||
if (!EnsureLogDirectory(output_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure that a log file exists for us to write to. */
|
||||
if (!GenerateLogFile(m_log_file_path, sizeof(m_log_file_path), output_dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Write the log file header. */
|
||||
if (!WriteLogFileHeader(m_log_file_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Set our initial offset. */
|
||||
m_log_file_offset = LogFileHeaderSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SdCardLogger::Finalize() {
|
||||
this->SetEnabled(false);
|
||||
if (m_is_sd_card_mounted) {
|
||||
fs::Unmount(SdCardMountName);
|
||||
m_is_sd_card_mounted = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SdCardLogger::Write(const u8 *data, size_t size) {
|
||||
/* Only write if sd card logging is enabled. */
|
||||
if (!GetSdCardLoggingEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Ensure we keep our pre and post-conditions in check. */
|
||||
bool success = false;
|
||||
ON_SCOPE_EXIT {
|
||||
if (!success && m_is_sd_card_mounted) {
|
||||
fs::Unmount(SdCardMountName);
|
||||
m_is_sd_card_mounted = false;
|
||||
m_is_sd_card_status_unknown = true;
|
||||
}
|
||||
this->SetEnabled(success);
|
||||
};
|
||||
|
||||
/* Try to initialize. */
|
||||
if (!this->Initialize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Try to write the log file. */
|
||||
if (!WriteLogFileBody(m_log_file_path, m_log_file_offset, data, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
m_log_file_offset += size;
|
||||
|
||||
/* We succeeded. */
|
||||
success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
class SdCardLogger {
|
||||
AMS_SINGLETON_TRAITS(SdCardLogger);
|
||||
public:
|
||||
using LoggingObserver = void (*)(bool available);
|
||||
private:
|
||||
os::SdkMutex m_logging_observer_mutex;
|
||||
bool m_is_enabled;
|
||||
bool m_is_sd_card_mounted;
|
||||
bool m_is_sd_card_status_unknown;
|
||||
char m_log_file_path[0x80];
|
||||
s64 m_log_file_offset;
|
||||
LoggingObserver m_logging_observer;
|
||||
public:
|
||||
void Finalize();
|
||||
|
||||
void SetLoggingObserver(LoggingObserver observer);
|
||||
|
||||
bool Write(const u8 *data, size_t size);
|
||||
private:
|
||||
bool GetEnabled() const;
|
||||
void SetEnabled(bool enabled);
|
||||
|
||||
bool Initialize();
|
||||
};
|
||||
|
||||
}
|
78
libraries/libstratosphere/source/lm/srv/lm_time_util.cpp
Normal file
78
libraries/libstratosphere/source/lm/srv/lm_time_util.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "lm_time_util.hpp"
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit std::atomic_bool g_is_time_invalid = false;
|
||||
|
||||
constinit time::PosixTime InvalidPosixTime = { .value = 0 };
|
||||
|
||||
constexpr bool IsValidPosixTime(time::PosixTime time) {
|
||||
return time.value > 0;
|
||||
}
|
||||
|
||||
void EnsureTimeInitialized() {
|
||||
static constinit os::SdkMutex g_time_initialized_mutex;
|
||||
static constinit bool g_time_initialized = false;
|
||||
|
||||
if (AMS_UNLIKELY(!g_time_initialized)) {
|
||||
std::scoped_lock lk(g_time_initialized_mutex);
|
||||
|
||||
if (AMS_LIKELY(!g_time_initialized)) {
|
||||
R_ABORT_UNLESS(time::Initialize());
|
||||
g_time_initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
time::PosixTime GetCurrentTime() {
|
||||
/* Ensure that we can use time services. */
|
||||
EnsureTimeInitialized();
|
||||
|
||||
/* Repeatedly try to get a valid time. */
|
||||
for (auto wait_seconds = 1; wait_seconds <= 8; wait_seconds *= 2) {
|
||||
/* Get the standard user system clock time. */
|
||||
time::PosixTime current_time{};
|
||||
if (R_FAILED(time::StandardUserSystemClock::GetCurrentTime(std::addressof(current_time)))) {
|
||||
return InvalidPosixTime;
|
||||
}
|
||||
|
||||
/* If the time is valid, return it. */
|
||||
if (IsValidPosixTime(current_time)) {
|
||||
return current_time;
|
||||
}
|
||||
|
||||
/* Check if we've failed to get a time in the past. */
|
||||
if (g_is_time_invalid) {
|
||||
return InvalidPosixTime;
|
||||
}
|
||||
|
||||
/* Wait a bit before trying again. */
|
||||
os::SleepThread(TimeSpan::FromSeconds(wait_seconds));
|
||||
}
|
||||
|
||||
/* We failed to get a valid time. */
|
||||
g_is_time_invalid = true;
|
||||
return InvalidPosixTime;
|
||||
}
|
||||
|
||||
}
|
23
libraries/libstratosphere/source/lm/srv/lm_time_util.hpp
Normal file
23
libraries/libstratosphere/source/lm/srv/lm_time_util.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::lm::srv {
|
||||
|
||||
time::PosixTime GetCurrentTime();
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue