blob: 8530ef8a49993d7cc32a170723a0c415b03231f1 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_LD_PROCESSARGS_H_
#define LIB_LD_PROCESSARGS_H_
#include <lib/fdio/processargs.h>
#include <lib/zx/channel.h>
#include <lib/zx/result.h>
#include <zircon/processargs.h>
#include <zircon/types.h>
#include <array>
#include <cassert>
#include <concepts>
#include <cstddef>
#include <span>
#include <string_view>
#include <type_traits>
namespace ld {
// To avoid dynamic stack allocation (via variable-length arrays), use fixed
// limits for the size of the processargs protocol message. The version of the
// message sent to the dynamic linker (PT_INTERP) is always quite limited in
// contents. The system's maximum number of handles does not make for overly
// large stack buffers. The argument strings are not actually sent, and the
// environment strings only include the LD_DEBUG and/or LD_TRACE settings so
// the total string size will remain small.
constexpr uint32_t kProcessargsInterpStringSpace = 128;
// Given a whole <zircon/processargs.h> message buffer as a std::string_view,
// extract (up to) num strings by calling take as void(std::string_view). This
// count should match the zx_proc_args_t::*_num value corresponding to the
// zx_proc_args_t::*_off value passed here. The return value is exactly num if
// everything was normal, or shorter if the strings in the message were
// incorrectly truncated for the given *_off and *_num values from the header.
template <std::invocable<std::string_view> Take>
constexpr uint32_t GetProcessargsStrings( //
std::string_view message, uint32_t off, uint32_t num, Take&& take) {
if (off > message.size()) [[unlikely]] {
return 0;
}
std::string_view strings = message.substr(off);
for (uint32_t i = 0; i < num; ++i) {
size_t pos = strings.find_first_of('\0');
if (pos == std::string_view::npos) [[unlikely]] {
// Return a short count if the message is truncated.
return i;
}
take(strings.substr(0, pos));
strings.remove_prefix(pos + 1);
}
return num;
}
// Returns true if this is PA_FD type handle_info value value denotes the
// logging handle (stderr file descriptor).
constexpr bool IsProcessargsLogHandle(uint32_t info) {
return PA_HND_TYPE(info) == PA_FD && // Right type.
((PA_HND_ARG(info) & FDIO_FLAG_USE_FOR_STDIO) || // std{in,out,err}
PA_HND_ARG(info) == 2); // STDERR_FILENO
}
// This helps manage a buffer to receive a message on the bootstrap channel in
// the legacy <zircon/processargs.h> protocol. For the PT_INTERP message
// received by a startup dynamic linker, it can use small fixed buffer sizes.
// For the general processargs message (sent second when there is a PT_INTERP)
// that may be much larger, this can be used with 0 sizes and embedded as the
// first member in a larger buffer of dynamic size (or just `reinterpret_cast`
// from a sufficiently-aligned and -sized byte buffer pointer). Sizing and
// allocating the zx_handle_t buffer for the channel read is not handled here.
template <uint32_t StringSpace = kProcessargsInterpStringSpace,
uint32_t MaxHandles = ZX_CHANNEL_MAX_MSG_HANDLES>
struct ProcessargsBuffer {
using HandlesBuffer = std::array<zx_handle_t, MaxHandles>;
struct Actual {
constexpr auto operator<=>(const Actual&) const = default;
uint32_t bytes = 0, handles = 0;
};
// Returns true if the buffer starting with this and extending for a total of
// actual_bytes and with actual_handles corresponding handles (as read from
// the bootstrap channel) is a valid message in the <zircon/processargs.h>
// protocol. If so, it's safe to use the other accessor methods and public
// members below.
constexpr bool Valid(Actual actual) const {
auto valid_magic = [&]() {
return actual.bytes >= sizeof(header) && // Safe to look.
header.protocol == ZX_PROCARGS_PROTOCOL && // Magic number OK.
header.version == ZX_PROCARGS_VERSION; // Version OK.
};
auto valid_handles = [&]() {
return header.handle_info_off >= sizeof(header) &&
header.handle_info_off % alignof(uint32_t) == 0 &&
header.handle_info_off <= actual.bytes &&
actual.bytes - (header.handle_info_off / sizeof(uint32_t)) >= actual.handles;
};
auto valid_strings = [bytes = actual.bytes](uint32_t off, uint32_t num) {
return num == 0 || (off >= sizeof(zx_proc_args_t) &&
// The strings are only fully valid if there are
// `num` NUL terminators inside the buffer starting
// at `off`, but this just checks that it's even
// possible without actually finding all the NULs.
off < bytes && num <= bytes - off);
};
return valid_magic() && valid_handles() && //
valid_strings(header.args_off, header.args_num) &&
valid_strings(header.environ_off, header.environ_num) &&
valid_strings(header.names_off, header.names_num);
}
// Make sure the channel has a message ready to be read. The parent or
// service that started the process might have started this process before
// sending its bootstrap message.
static zx::result<> Wait(zx::unowned_channel bootstrap) {
zx_signals_t pending;
zx_status_t status = bootstrap->wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), &pending);
if (status != ZX_OK) [[unlikely]] {
return zx::error{status};
}
assert(pending & ZX_CHANNEL_READABLE);
return zx::ok();
}
// Wait as necessary and read the message into the buffer formed by this
// object and optional additional buffer space after it if the optional
// num_bytes argument is passed with the total buffer size starting at this.
// The buffer size (sizeof(*this) or implicit) must be large enough for a
// message that could fill all of handles.size() with handles. On successful
// return, the Actual::handles leading subspan of handles have been filled
// along with the Actual::bytes of this buffer. The message must be checked
// with Valid() before the accessor methods below are used.
zx::result<Actual> Read(zx::unowned_channel bootstrap, std::span<zx_handle_t> handles,
uint32_t num_bytes = sizeof(ProcessargsBuffer)) {
assert(num_bytes >= sizeof(*this));
assert(handles.size() >= handle_info_space.size());
if (zx::result wait = Wait(bootstrap->borrow()); wait.is_error()) {
return wait.take_error();
}
// Read the message into the buffer.
const uint32_t num_handles = static_cast<uint32_t>(handles.size());
Actual actual;
zx_status_t status = bootstrap->read(0, this, handles.data(), num_bytes, num_handles,
&actual.bytes, &actual.handles);
return zx::make_result(status, actual);
}
// Wait as necessary and then peek to see how much buffer space is needed for
// the pending message.
static zx::result<Actual> Peek(zx::unowned_channel bootstrap) {
if (zx::result wait = Wait(bootstrap->borrow()); wait.is_error()) {
return wait.take_error();
}
Actual actual;
zx_status_t status = bootstrap->read(0, nullptr, nullptr, 0, 0, &actual.bytes, &actual.handles);
if (status == ZX_ERR_BUFFER_TOO_SMALL) [[likely]] {
status = ZX_OK; // This is the normal case.
}
return zx::make_result(status, actual);
}
// Read after successful Peek(), where the Actual Read() would return on
// success is already known.
zx::result<> ReadAfterPeek(zx::unowned_channel bootstrap, Actual num,
std::span<zx_handle_t> handles) {
assert(handles.size() >= num.handles);
Actual actual;
if (zx_status_t status = bootstrap->read(0, this, handles.data(), num.bytes, num.handles,
&actual.bytes, &actual.handles);
status != ZX_OK) [[unlikely]] {
return zx::error{status};
}
assert(actual == num);
return zx::ok();
}
// Get the whole message buffer as raw chars.
std::string_view message_chars(uint32_t actual_bytes) const {
return {reinterpret_cast<const char*>(this), actual_bytes};
}
// Get the whole handle info table in the message.
std::span<const uint32_t> handle_info(uint32_t actual_handles) const {
const size_t off = header.handle_info_off / sizeof(uint32_t);
return {reinterpret_cast<const uint32_t*>(this) + off, actual_handles};
}
std::span<uint32_t> handle_info(uint32_t actual_handles) {
const size_t off = header.handle_info_off / sizeof(uint32_t);
return {reinterpret_cast<uint32_t*>(this) + off, actual_handles};
}
// This returns chars containing at least the environ strings, but
// possibly more. This can be used to scan the NUL-terminated sequences
// directly rather than splitting into the expected number of strings via
// the environ_strings() method.
std::string_view environ_chars(uint32_t actual_bytes) const {
if (header.environ_num == 0) {
return {};
}
return message_chars(actual_bytes).substr(header.environ_off);
}
uint32_t GetArgsStrings(uint32_t actual_bytes, std::invocable<std::string_view> auto take) const {
return GetProcessargsStrings(message_chars(actual_bytes), header.args_off, header.args_num,
std::move(take));
}
uint32_t GetEnvStrings(uint32_t actual_bytes, std::invocable<std::string_view> auto take) const {
return GetProcessargsStrings(message_chars(actual_bytes), header.environ_off,
header.environ_num, std::move(take));
}
uint32_t GetNameStrings(uint32_t actual_bytes, std::invocable<std::string_view> auto take) const {
return GetProcessargsStrings(message_chars(actual_bytes), header.names_off, header.names_num,
std::move(take));
}
// Aside from the header, this is not necessarily the actual layout of the
// message buffer that will be received (though it's the optimal one for the
// sender to choose), but it serves to approximate the maximum size it's
// reasonable to handle. The actual handle_info and strings portions of the
// buffer are at whatever offsets the header fields indicate.
zx_proc_args_t header; // Must be first.
[[no_unique_address]] std::array<uint32_t, MaxHandles> handle_info_space;
[[no_unique_address]] std::array<char, StringSpace> string_space;
};
static_assert(std::is_trivially_default_constructible_v<ProcessargsBuffer<>>);
static_assert(offsetof(ProcessargsBuffer<>, header) == 0);
// The default template parameters result in a large but not unreasonable
// contribution to the stack frame size.
static_assert(sizeof(ProcessargsBuffer<>) < 1024);
// Just the header alone contributing to a dynamic buffer size is not large.
static_assert(sizeof(ProcessargsBuffer<0, 0>) < 48);
} // namespace ld
#endif // LIB_LD_PROCESSARGS_H_