Atmosphere/libraries/libstratosphere/source/lm/srv/lm_log_buffer.cpp
SciresM e9849c74cf
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.
2021-09-11 19:32:14 -07:00

208 lines
7.2 KiB
C++

/*
* 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;
}
}