blob: 558651256fbdd7d1efc4d5a1a545fbeb6c1a8384 [file] [log] [blame]
// Copyright 2020 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.
// Copyright 2018 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/developer/debug/zxdb/debug_adapter/server.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <unistd.h>
#include "src/developer/debug/shared/logging/logging.h"
namespace zxdb {
DebugAdapterServer::DebugAdapterServer(Session* session, uint16_t port)
: session_(session), port_(port) {
int fd[2] = {};
pipe(fd);
exit_pipe_[0] = fbl::unique_fd(fd[0]);
exit_pipe_[1] = fbl::unique_fd(fd[1]);
}
Err DebugAdapterServer::Init() {
main_loop_ = debug_ipc::MessageLoop::Current();
server_socket_.reset(socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP));
if (!server_socket_.is_valid()) {
return Err("Could not create socket.");
}
{
// Set SO_REUSEPORT so that subsequent binds succeeds.
int opt = 1;
setsockopt(server_socket_.get(), SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
}
{
// Bind to local address.
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(port_);
if (bind(server_socket_.get(), reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
return Err("Could not bind socket to port: %d", port_);
}
}
if (listen(server_socket_.get(), 1) < 0) {
return Err("Failed to listen on server socket");
}
ListenConnection();
return Err();
}
void DebugAdapterServer::ListenConnection() {
FX_DCHECK(!background_thread_.get()); // Duplicate ListenConnection() call.
// Create the background thread to listen to incoming connections.
background_thread_ = std::make_unique<std::thread>([this]() { ListenBackgroundThread(); });
}
void DebugAdapterServer::ListenBackgroundThread() {
// Wait for one connection.
// TODO(puneetha) : Replace FX_LOGS with call to console output.
FX_LOGS(INFO) << "Waiting on port " << port_ << " for debug adapter connection.\r\n";
fbl::unique_fd client;
while (!Accept(client)) {
if (background_thread_exit_) {
return;
}
}
FX_LOGS(INFO) << "Debug Adapter connection established.\r\n";
main_loop_->PostTask(FROM_HERE, [this, client = std::move(client)]() mutable {
ConnectionResolvedMainThread(std::move(client));
});
}
bool DebugAdapterServer::Accept(fbl::unique_fd& client) {
// Wait on server_socket fd and exit_event fd until new connection is received or thread exit is
// requested.
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(server_socket_.get(), &read_set);
FD_SET(exit_pipe_[0].get(), &read_set);
int nfds =
server_socket_.get() > exit_pipe_[0].get() ? server_socket_.get() : exit_pipe_[0].get();
auto status = select(nfds + 1, &read_set, NULL, NULL, NULL);
if (status <= 0) {
// An error or timeout occured.
return false;
}
if (FD_ISSET(exit_pipe_[0].get(), &read_set)) {
// Thread exit requested.
return false;
}
// Accept the new connection.
sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
socklen_t addrlen = sizeof(addr);
client =
fbl::unique_fd(accept(server_socket_.get(), reinterpret_cast<sockaddr*>(&addr), &addrlen));
if (!client.is_valid()) {
FX_LOGS(ERROR) << "Accept failed.";
return false;
}
if (fcntl(client.get(), F_SETFL, O_NONBLOCK) < 0) {
FX_LOGS(ERROR) << "Couldn't make port nonblocking.";
return false;
}
return true;
}
void DebugAdapterServer::ConnectionResolvedMainThread(fbl::unique_fd client) {
background_thread_->join();
background_thread_.reset();
for (auto& observer : observers_) {
observer.ClientConnected();
}
buffer_ = std::make_unique<debug_ipc::BufferedFD>();
if (!buffer_->Init(std::move(client))) {
FX_LOGS(ERROR) << "Failed to initialize debug adapter buffer";
return;
}
context_ = std::make_unique<DebugAdapterContext>(session_, &buffer_->stream());
buffer_->set_data_available_callback(
[context = context_.get()]() { context->OnStreamReadable(); });
// Reset the client connection on error.
buffer_->set_error_callback([this]() {
FX_LOGS(INFO) << "Connection lost.";
OnConnectionError();
});
}
void DebugAdapterServer::OnConnectionError() {
ResetClientConnection();
for (auto& observer : observers_) {
observer.ClientDisconnected();
}
ListenConnection();
}
void DebugAdapterServer::ResetClientConnection() {
context_.reset();
buffer_.reset();
}
DebugAdapterServer::~DebugAdapterServer() {
ResetClientConnection();
if (background_thread_) {
background_thread_exit_ = true;
// Write to exit_event to unblock select().
int ret;
do {
int val = 1;
ret = write(exit_pipe_[1].get(), &val, sizeof(val));
} while (ret < 0 && errno == EINTR);
// Wait for background thread to exit.
background_thread_->join();
background_thread_.reset();
}
}
} // namespace zxdb