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:
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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user