Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions score/launch_manager/src/daemon/src/common/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ cc_library(
}),
)

cc_library(
name = "signal_safe_log",
hdrs = ["signal_safe_log.hpp"],
include_prefix = "score/mw/launch_manager/common",
strip_include_prefix = "/score/launch_manager/src/daemon/src/common",
visibility = ["//score/launch_manager/src/daemon:__subpackages__"],
)

cc_library(
name = "constants",
hdrs = ["constants.hpp"],
Expand Down
125 changes: 125 additions & 0 deletions score/launch_manager/src/daemon/src/common/signal_safe_log.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/********************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

#include <charconv>
#include <unistd.h>
#include <array>
#include <cstdlib>
#include <string_view>

#ifndef SIGNAL_SAFE_LOG_HPP_INCLUDED
#define SIGNAL_SAFE_LOG_HPP_INCLUDED

namespace
{

/// @brief Utility for building log messages in an async signal safe context.
template <int C>
class signal_safe_buffer
{
public:
std::array<char, C> buffer = {};
size_t start = 0;
size_t end = 0;

/// @brief Append the given string to the buffer.
template <typename T, std::enable_if_t<std::is_convertible_v<T, std::string_view>, bool> = true>
void append(const T& value)
{
const auto string = std::string_view(value);

size_t length = std::min(string.length(), buffer.size() - end);

for (size_t index = 0; index < length; ++index)
{
buffer[end++] = string[index];
}
}

/// @brief Append the given integer to the buffer.
template <typename T, std::enable_if_t<std::is_integral_v<T>, bool> = true>
void append(const T& value)
{
const auto [pointer, error] = std::to_chars(&buffer[end], &buffer.back(), value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the conclusion on using std::to_chars in a signal-safe context?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://gcc.gnu.org/onlinedocs/gcc-13.1.0/libstdc++/api/a00062_source.html (line 130)

It only accesses local variables and the buffer it was given. I don't see anything which stands out to me as unsafe


if (error == std::errc{})
{
end = pointer - &buffer.front();
}
}

/// @brief Write out the buffer to standard error.
/// @return Whether the write succeeded. True means the whole message was
/// written. False means nothing, or only part was written.
[[nodiscard]] bool flush()
{
while (start < end)
{
const auto written = write(STDERR_FILENO, &buffer[start], end - start);

if (written >= 0)
{
start += written;
}
else if (errno != EINTR)
{
return false;
}
}

// Rewind the indices in case the buffer is used again.
start = 0;
end = 0;

return true;
}
};

} // namespace

namespace score::lcm::internal
{

template <typename... T>
[[nodiscard]] bool signal_safe_log(const T&... values)
{
const int original_errno = errno; // For reentrancy

signal_safe_buffer<1024> buffer = {};
(buffer.append(values), ...);
buffer.append("\n");
const bool written = buffer.flush();

errno = original_errno; // For reentrancy

return written;
}

template <typename... T>
[[nodiscard]] bool signal_safe_log_errno(int log_errno, const T&... values)
{
#if _GNU_SOURCE && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 32
// strerrordesc_np is documented as async signal safe.
return signal_safe_log(values..., " (", strerrordesc_np(log_errno), ")");
#elif __QNXNTO__
// QNX strerror is documented as async signal safe.
return signal_safe_log(values..., " (", strerror(log_errno), ")");
#else
// POSIX strerror is not async signal safe. Log the raw number instead.
return signal_safe_log(values..., " (errno ", log_errno, ")");
#endif
}

} // namespace score::lcm::internal

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ cc_library(
visibility = ["//score/launch_manager/src/daemon/src/process_group_manager:__pkg__"],
deps = [
"//score/launch_manager/src/daemon/src/common:log",
"//score/launch_manager/src/daemon/src/common:signal_safe_log",
"//score/launch_manager/src/daemon/src/control:control_client_channel",
"//score/launch_manager/src/daemon/src/osal:ipc_comms",
"//score/launch_manager/src/daemon/src/osal:security_policy",
Expand Down
Loading
Loading