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:
SciresM 2021-09-11 19:32:14 -07:00 committed by GitHub
parent a1fb8a91c8
commit e9849c74cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
94 changed files with 5595 additions and 45 deletions

View file

@ -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;
}
}

View file

@ -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();
};
}

View file

@ -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;
}
}

View file

@ -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();
};
}

View 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));
}
}

View 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;
}
}

View 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;
}
}

View 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);
};
}

View 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();
}
}

View 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>);
}

View file

@ -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;
}
}

View file

@ -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;
}
};
}

View 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;
}
}
}

View file

@ -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);
};
}

View 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();
}
}

View file

@ -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();
}

View file

@ -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();
}
}

View file

@ -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>);
}

View 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();
}
}

View 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>);
}

View 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;
}
}

View 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/>.
*/
#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();
};
}

View 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;
}
}

View 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();
}