blob: 34cff67be94dff6172cc78a00bec49b0ac975b07 [file] [log] [blame]
// Copyright 2022 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.
#include "src/sys/early_boot_instrumentation/coverage_source.h"
#include <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.boot/cpp/wire.h>
#include <fidl/fuchsia.debugdata/cpp/wire.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fdio/io.h>
#include <lib/fidl/cpp/wire/channel.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/stdcompat/source_location.h>
#include <lib/stdcompat/span.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/vfs/cpp/service.h>
#include <lib/zx/channel.h>
#include <lib/zx/eventpair.h>
#include <lib/zx/result.h>
#include <lib/zx/time.h>
#include <unistd.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <zircon/fidl.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <array>
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <fbl/unique_fd.h>
#include <sdk/lib/vfs/cpp/pseudo_dir.h>
#include <sdk/lib/vfs/cpp/vmo_file.h>
namespace early_boot_instrumentation {
namespace {
zx::result<> ExportBootDebugData(vfs::PseudoDir& out_dir, fbl::unique_fd fd,
std::string_view export_as) {
// Get the underlying vmo of the fd.
zx::vmo vmo;
if (auto res = fdio_get_vmo_exact(fd.get(), vmo.reset_and_get_address()); res != ZX_OK) {
FX_LOGS(INFO) << 1;
return zx::error(res);
}
size_t size = 0;
if (auto res = vmo.get_prop_content_size(&size); res != ZX_OK) {
FX_LOGS(INFO) << 2;
return zx::error(res);
}
auto file = std::make_unique<vfs::VmoFile>(std::move(vmo), size);
if (auto res = out_dir.AddEntry(std::string(export_as), std::move(file)); res != ZX_OK) {
FX_LOGS(INFO) << 3;
return zx::error(res);
}
return zx::success();
}
template <typename HandleType>
bool IsSignalled(const HandleType& handle, zx_signals_t signal) {
zx_signals_t actual = 0;
auto status = handle.wait_one(signal, zx::time::infinite_past(), &actual);
return (status == ZX_OK || status == ZX_ERR_TIMED_OUT) && (actual & signal) != 0;
}
enum class DataType {
kDynamic,
kStatic,
};
constexpr std::string_view DataTypeDir(DataType t) {
switch (t) {
case DataType::kDynamic:
return kDynamicDir;
case DataType::kStatic:
return kStaticDir;
default:
return "Unknown DataType.";
}
}
// Returns or creates the respective instance for a given |sink_name|.
vfs::PseudoDir& GetOrCreate(std::string_view sink_name, DataType type, SinkDirMap& sink_map) {
auto it = sink_map.find(sink_name);
// If it's the first time we see this sink, fill up the base hierarchy:
// root
// + /static
// + /dynamic
if (it == sink_map.end()) {
FX_LOGS(INFO) << "Encountered sink " << sink_name << " static and dynamic subdirs created";
it = sink_map.insert(std::make_pair(sink_name, std::make_unique<vfs::PseudoDir>())).first;
it->second->AddEntry(std::string(kStaticDir), std::make_unique<vfs::PseudoDir>());
it->second->AddEntry(std::string(kDynamicDir), std::make_unique<vfs::PseudoDir>());
}
std::string path(DataTypeDir(type));
auto& root_dir = *(it->second);
vfs::Node* node = nullptr;
// Both subdirs should always be available.
ZX_ASSERT(root_dir.Lookup(path, &node) == ZX_OK);
return *reinterpret_cast<vfs::PseudoDir*>(node);
}
} // namespace
zx::result<> ExposeBootDebugdata(fbl::unique_fd& debugdata_root, SinkDirMap& sink_map) {
// Iterate on every entry in the directory.
static constexpr auto for_each_dentry = [](DIR* root, auto&& visitor) {
// Borrow underlying FD for opening relative files.
int root_fd = dirfd(root);
while (auto* dentry = readdir(root)) {
std::string_view dentry_name(dentry->d_name);
if (dentry_name == "." || dentry_name == "..") {
continue;
}
fbl::unique_fd entry_fd(openat(root_fd, dentry->d_name, O_RDONLY));
if (!entry_fd) {
FX_LOGS(INFO) << "Failed to obtain FD for " << dentry->d_name << ". " << strerror(errno);
continue;
}
visitor(dentry, std::move(entry_fd));
}
};
DIR* root = fdopendir(debugdata_root.get());
if (!root) {
FX_LOGS(INFO) << "Failed to obtain DIR entry from FD. " << strerror(errno);
return zx::error(ZX_ERR_INVALID_ARGS);
}
// Taken by fdopendir.
debugdata_root.release();
auto for_each_debugdata = [&sink_map](std::string_view sink_name, DataType type,
struct dirent* entry, fbl::unique_fd debugdata_fd) {
auto res = ExportBootDebugData(GetOrCreate(sink_name, type, sink_map), std::move(debugdata_fd),
entry->d_name);
if (res.is_error()) {
FX_LOGS(ERROR) << "Failed to export boot debugdata to: " << sink_name << "/"
<< DataTypeDir(type) << "/" << entry->d_name;
return;
}
FX_LOGS(INFO) << " Exported boot debugdata to " << sink_name << "/" << DataTypeDir(type) << "/"
<< entry->d_name;
};
// Each sink contains at most two entries, mapped to either static or dynamic data.
// "s" or "d", each of them a directory.
auto for_each_sink = [&for_each_debugdata](struct dirent* entry, fbl::unique_fd sink_dir_fd) {
std::string_view sink_name(entry->d_name);
fbl::unique_fd static_dir_fd(openat(sink_dir_fd.get(), "s", O_DIRECTORY | O_RDONLY));
if (static_dir_fd) {
DIR* static_dir = fdopendir(static_dir_fd.get());
if (static_dir) {
static_dir_fd.release();
for_each_dentry(static_dir, [&](auto* dentry, fbl::unique_fd debugdata_fd) {
for_each_debugdata(sink_name, DataType::kStatic, dentry, std::move(debugdata_fd));
});
closedir(static_dir);
}
}
fbl::unique_fd dynamic_dir_fd(openat(sink_dir_fd.get(), "d", O_DIRECTORY | O_RDONLY));
if (dynamic_dir_fd) {
DIR* dynamic_dir = fdopendir(dynamic_dir_fd.release());
if (dynamic_dir) {
dynamic_dir_fd.release();
for_each_dentry(dynamic_dir, [&](auto* dentry, fbl::unique_fd debugdata_fd) {
for_each_debugdata(sink_name, DataType::kDynamic, dentry, std::move(debugdata_fd));
});
closedir(dynamic_dir);
}
}
};
// Each entry in the root is a directory named after the sink.
for_each_dentry(root, for_each_sink);
closedir(root);
return zx::success();
}
namespace {
class Server : public fidl::WireServer<fuchsia_boot::SvcStash>,
public fidl::WireServer<fuchsia_io::Openable>,
public fidl::WireServer<fuchsia_debugdata::Publisher> {
public:
explicit Server(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
SinkDirMap TakeSinkToDir() { return std::move(sink_to_dir_); }
private:
void Publish(PublishRequestView request, PublishCompleter::Sync& completer) override {
DataType published_data_type = IsSignalled(request->vmo_token, ZX_EVENTPAIR_PEER_CLOSED)
? DataType::kStatic
: DataType::kDynamic;
auto& dir = GetOrCreate(request->data_sink.get(), published_data_type, sink_to_dir_);
std::array<char, ZX_MAX_NAME_LEN> name_buff = {};
auto name = std::to_string(svc_id_) + "-" + std::to_string(req_id_);
if (zx_status_t status =
request->data.get_property(ZX_PROP_NAME, name_buff.data(), name_buff.size());
status == ZX_OK) {
std::string name_prop(name_buff.data());
if (!name_prop.empty()) {
name += "." + name_prop;
}
}
uint64_t size;
if (zx_status_t status = request->data.get_prop_content_size(&size); status != ZX_OK) {
FX_PLOGS(INFO, status) << "Failed to obtain vmo content size. Attempting to use vmo size.";
if (zx_status_t status = request->data.get_size(&size); status != ZX_OK) {
FX_PLOGS(INFO, status) << "Failed to obtain vmo size.";
size = 0;
}
}
FX_LOGS(INFO) << "Exposing " << request->data_sink.get() << "/"
<< (published_data_type == DataType::kStatic ? "static/" : "dynamic/") << name
<< " size: " << size << " bytes";
dir.AddEntry(std::move(name), std::make_unique<vfs::VmoFile>(std::move(request->data), size));
++req_id_;
}
void Open(OpenRequestView request, OpenCompleter::Sync& completer) override {
if (request->path.get() == fidl::DiscoverableProtocolName<fuchsia_debugdata::Publisher>) {
FX_LOGS(INFO) << "Encountered open request to debugdata.Publisher";
publisher_bindings_.AddBinding(
dispatcher_, fidl::ServerEnd<fuchsia_debugdata::Publisher>{request->object.TakeChannel()},
this, fidl::kIgnoreBindingClosure);
} else {
FX_LOGS(INFO) << "Encountered open request to unhandled path: " << request->path.get();
}
}
void Store(StoreRequestView request, StoreCompleter::Sync& completer) override {
FX_LOGS(INFO) << "Encountered stashed svc handle";
fidl::ServerEnd<fuchsia_io::Directory>& directory = request->svc_endpoint;
openable_bindings_.AddBinding(dispatcher_,
fidl::ServerEnd<fuchsia_io::Openable>{directory.TakeChannel()},
this, [](Server* impl, fidl::UnbindInfo) {
impl->req_id_ = 0;
impl->svc_id_++;
});
}
async_dispatcher_t* const dispatcher_;
SinkDirMap sink_to_dir_;
// used for name generation.
int svc_id_ = 0;
int req_id_ = 0;
fidl::ServerBindingGroup<fuchsia_io::Openable> openable_bindings_;
fidl::ServerBindingGroup<fuchsia_debugdata::Publisher> publisher_bindings_;
};
} // namespace
SinkDirMap ExtractDebugData(fidl::ServerEnd<fuchsia_boot::SvcStash> svc_stash) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
Server server(loop.dispatcher());
fidl::BindServer(loop.dispatcher(), std::move(svc_stash), &server);
loop.RunUntilIdle();
return server.TakeSinkToDir();
}
} // namespace early_boot_instrumentation