| // Copyright 2026 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 <lib/driver/debug/cpp/debug_server.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <format> |
| #include <iterator> |
| #include <string_view> |
| |
| namespace fdf_debug { |
| |
| namespace { |
| |
| void WriteToSocket(const zx::socket& socket, std::string_view data) { |
| if (data.empty() || !socket.is_valid()) { |
| return; |
| } |
| const char* ptr = data.data(); |
| size_t remaining = data.size(); |
| while (remaining > 0) { |
| size_t actual = 0; |
| zx_status_t status = socket.write(0, ptr, remaining, &actual); |
| if (status != ZX_OK) { |
| break; |
| } |
| ptr += actual; |
| remaining -= actual; |
| } |
| } |
| |
| void ReplyWithResult(zx::socket stdout_socket, zx::socket stderr_socket, |
| const ExecutionResult& result, |
| DebugServer::ExecuteCompleter::Async completer) { |
| WriteToSocket(stdout_socket, result.stdout_()); |
| stdout_socket.reset(); |
| WriteToSocket(stderr_socket, result.stderr_()); |
| stderr_socket.reset(); |
| |
| fuchsia_driver_debug::DebugExecuteResponse response; |
| response.exit_code() = result.exit_code(); |
| completer.Reply(fit::ok(std::move(response))); |
| } |
| |
| } // namespace |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, |
| StringCommandHandler handler) { |
| RegisterCommand(std::move(name), std::move(description), "", std::move(handler)); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, std::string usage, |
| StringCommandHandler handler) { |
| RegisterCommand( |
| std::move(name), std::move(description), std::move(usage), |
| AsyncCommandHandler([h = std::move(handler)](cpp20::span<const std::string> args, |
| fit::callback<void(ExecutionResult)> completer) { |
| auto res = h(args); |
| ExecutionResult result; |
| if (res.is_ok()) { |
| result.stdout_() = std::move(res.value()); |
| result.stderr_() = ""; |
| result.exit_code() = 0; |
| } else { |
| result.stdout_() = ""; |
| result.stderr_() = std::move(res.error_value()); |
| result.exit_code() = 1; |
| } |
| completer(std::move(result)); |
| })); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, |
| StatusStringCommandHandler handler) { |
| RegisterCommand(std::move(name), std::move(description), "", std::move(handler)); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, std::string usage, |
| StatusStringCommandHandler handler) { |
| RegisterCommand( |
| std::move(name), std::move(description), std::move(usage), |
| AsyncCommandHandler([h = std::move(handler)](cpp20::span<const std::string> args, |
| fit::callback<void(ExecutionResult)> completer) { |
| auto res = h(args); |
| ExecutionResult result; |
| if (res.is_ok()) { |
| result.stdout_() = std::move(res.value()); |
| result.stderr_() = ""; |
| result.exit_code() = 0; |
| } else { |
| result.stdout_() = ""; |
| result.stderr_() = std::string(zx_status_get_string(res.error_value())) + "\n"; |
| result.exit_code() = 1; |
| } |
| completer(std::move(result)); |
| })); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, |
| SimpleStringCommandHandler handler) { |
| RegisterCommand(std::move(name), std::move(description), "", std::move(handler)); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, std::string usage, |
| SimpleStringCommandHandler handler) { |
| RegisterCommand( |
| std::move(name), std::move(description), std::move(usage), |
| AsyncCommandHandler([h = std::move(handler)](cpp20::span<const std::string> args, |
| fit::callback<void(ExecutionResult)> completer) { |
| ExecutionResult result; |
| result.stdout_() = h(args); |
| result.stderr_() = ""; |
| result.exit_code() = 0; |
| completer(std::move(result)); |
| })); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, |
| FullCommandHandler handler) { |
| RegisterCommand(std::move(name), std::move(description), "", std::move(handler)); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, std::string usage, |
| FullCommandHandler handler) { |
| RegisterCommand( |
| std::move(name), std::move(description), std::move(usage), |
| AsyncCommandHandler([h = std::move(handler)](cpp20::span<const std::string> args, |
| fit::callback<void(ExecutionResult)> completer) { |
| completer(h(args)); |
| })); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, |
| AsyncCommandHandler handler) { |
| RegisterCommand(std::move(name), std::move(description), "", std::move(handler)); |
| } |
| |
| void DebugServer::RegisterCommand(std::string name, std::string description, std::string usage, |
| AsyncCommandHandler handler) { |
| commands_.push_back(CommandEntry{ |
| .name = std::move(name), |
| .description = std::move(description), |
| .usage = std::move(usage), |
| .handler = std::move(handler), |
| }); |
| } |
| |
| zx::result<> DebugServer::Serve(async_dispatcher_t* dispatcher, |
| component::OutgoingDirectory& outgoing) { |
| return outgoing.AddUnmanagedProtocol<fuchsia_driver_debug::Debug>(CreateHandler(dispatcher)); |
| } |
| |
| zx::result<> DebugServer::Serve(async_dispatcher_t* dispatcher, fdf::OutgoingDirectory& outgoing) { |
| return outgoing.component().AddUnmanagedProtocol<fuchsia_driver_debug::Debug>( |
| CreateHandler(dispatcher)); |
| } |
| |
| zx::result<> DebugServer::Serve(async_dispatcher_t* dispatcher, |
| const std::shared_ptr<fdf::OutgoingDirectory>& outgoing) { |
| if (!outgoing) { |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| return Serve(dispatcher, *outgoing); |
| } |
| |
| void DebugServer::Bind(async_dispatcher_t* dispatcher, |
| fidl::ServerEnd<fuchsia_driver_debug::Debug> server_end) { |
| bindings_.AddBinding(dispatcher, std::move(server_end), this, fidl::kIgnoreBindingClosure); |
| } |
| |
| fidl::ProtocolHandler<fuchsia_driver_debug::Debug> DebugServer::CreateHandler( |
| async_dispatcher_t* dispatcher) { |
| return bindings_.CreateHandler(this, dispatcher, fidl::kIgnoreBindingClosure); |
| } |
| |
| void DebugServer::Execute(ExecuteRequest& request, ExecuteCompleter::Sync& completer) { |
| const std::vector<std::string>& args = request.args(); |
| zx::socket stdout_socket = std::move(request.stdout_()); |
| zx::socket stderr_socket = std::move(request.stderr_()); |
| |
| if (args.empty() || args[0] == "--help" || args[0] == "-h" || args[0] == "help") { |
| ExecutionResult result; |
| result.stdout_() = GenerateHelp(); |
| result.stderr_() = ""; |
| result.exit_code() = 0; |
| ReplyWithResult(std::move(stdout_socket), std::move(stderr_socket), result, |
| completer.ToAsync()); |
| return; |
| } |
| |
| const std::string& cmd_name = args[0]; |
| auto it = std::find_if(commands_.begin(), commands_.end(), |
| [&cmd_name](const CommandEntry& entry) { return entry.name == cmd_name; }); |
| |
| if (it == commands_.end()) { |
| ExecutionResult result; |
| result.stdout_() = ""; |
| result.stderr_() = "Unrecognized command: " + cmd_name + "\n"; |
| result.exit_code() = 2; |
| ReplyWithResult(std::move(stdout_socket), std::move(stderr_socket), result, |
| completer.ToAsync()); |
| return; |
| } |
| |
| if (args.size() > 1 && (args[1] == "--help" || args[1] == "-h")) { |
| ExecutionResult result; |
| result.stdout_() = GenerateCommandHelp(*it); |
| result.stderr_() = ""; |
| result.exit_code() = 0; |
| ReplyWithResult(std::move(stdout_socket), std::move(stderr_socket), result, |
| completer.ToAsync()); |
| return; |
| } |
| |
| auto async_completer = completer.ToAsync(); |
| cpp20::span<const std::string> sub_args = cpp20::span(args).subspan(1); |
| it->handler(sub_args, [async_completer = std::move(async_completer), |
| stdout_socket = std::move(stdout_socket), |
| stderr_socket = std::move(stderr_socket)](ExecutionResult result) mutable { |
| ReplyWithResult(std::move(stdout_socket), std::move(stderr_socket), result, |
| std::move(async_completer)); |
| }); |
| } |
| |
| void DebugServer::ListCommands(ListCommandsCompleter::Sync& completer) { |
| std::vector<fuchsia_driver_debug::CommandInfo> command_infos; |
| command_infos.reserve(commands_.size()); |
| for (const auto& cmd : commands_) { |
| fuchsia_driver_debug::CommandInfo info; |
| info.name() = cmd.name; |
| info.description() = cmd.description; |
| command_infos.push_back(std::move(info)); |
| } |
| fuchsia_driver_debug::DebugListCommandsResponse response; |
| response.commands() = std::move(command_infos); |
| completer.Reply(fit::ok(std::move(response))); |
| } |
| |
| void DebugServer::handle_unknown_method( |
| fidl::UnknownMethodMetadata<fuchsia_driver_debug::Debug> metadata, |
| fidl::UnknownMethodCompleter::Sync& completer) { |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| std::string DebugServer::GenerateHelp() const { |
| std::string help = "Usage: debug <command> [<args>]\n\n"; |
| help += "Driver debug commands.\n\n"; |
| help += "Options:\n"; |
| help += " --help display usage information\n\n"; |
| help += "Commands:\n"; |
| |
| size_t max_name_len = 14; |
| for (const auto& cmd : commands_) { |
| max_name_len = std::max(max_name_len, cmd.name.length()); |
| } |
| |
| for (const auto& cmd : commands_) { |
| std::format_to(std::back_inserter(help), " {:<{}} {}\n", cmd.name, max_name_len, |
| cmd.description); |
| } |
| |
| return help; |
| } |
| |
| std::string DebugServer::GenerateCommandHelp(const CommandEntry& cmd) const { |
| std::string usage_suffix = cmd.usage.empty() ? "[<args>]" : cmd.usage; |
| std::string help = "Usage: debug " + cmd.name + " " + usage_suffix + "\n\n"; |
| help += cmd.description + "\n"; |
| return help; |
| } |
| |
| } // namespace fdf_debug |