| // 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. | |
| #include "src/developer/shell/mirror/server.h" | |
| #include <condition_variable> | |
| #include <mutex> | |
| #include <thread> | |
| #include "src/developer/shell/mirror/common.h" | |
| namespace shell::mirror { | |
| #define PRINT_FLUSH(...) \ | |
| printf(__VA_ARGS__); \ | |
| fflush(stdout); | |
| // SocketConnection -------------------------------------------------------------------------------- | |
| uint64_t SocketConnection::global_id_ = 0; | |
| SocketConnection::~SocketConnection() {} | |
| Err SocketConnection::Accept(debug_ipc::MessageLoop* main_thread_loop, int server_fd) { | |
| sockaddr_in6 addr; | |
| memset(&addr, 0, sizeof(addr)); | |
| socklen_t addrlen = sizeof(addr); | |
| fbl::unique_fd client( | |
| accept4(server_fd, reinterpret_cast<sockaddr*>(&addr), &addrlen, SOCK_NONBLOCK)); | |
| if (!client.is_valid()) { | |
| return Err(kConnection, "Couldn't accept connection."); | |
| } | |
| main_thread_loop->PostTask( | |
| FROM_HERE, [this, client = std::move(client), | |
| server_loop = debug_ipc::MessageLoop::Current()]() mutable { | |
| if (!buffer_.Init(std::move(client))) { | |
| FX_LOGS(ERROR) << "Error waiting for data."; | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| return; | |
| } | |
| buffer_.set_data_available_callback( | |
| [buffer = &buffer_, path = this->server_->GetPath(), server_loop, connection = this]() { | |
| debug_ipc::StreamBuffer stream = buffer->stream(); | |
| char buf[32]; | |
| size_t len = stream.Read(buf, 32); | |
| if (len >= strlen(remote_commands::kQuitCommand) && | |
| strncmp(remote_commands::kQuitCommand, buf, len) == 0) { | |
| debug_ipc::MessageLoop::Current()->QuitNow(); | |
| server_loop->QuitNow(); | |
| return; | |
| } | |
| if (len >= strlen(remote_commands::kFilesCommand) && | |
| strncmp(remote_commands::kFilesCommand, buf, len) == 0) { | |
| Update update(&stream, &path); | |
| update.SendUpdates(); | |
| } else { | |
| FX_LOGS(ERROR) << "Unrecognized command from socket"; | |
| } | |
| connection->UnregisterAndDestroy(); | |
| }); | |
| #if 0 | |
| // TODO(jeremymanson): Should probably do something sensible here. | |
| buffer_.set_error_callback([]() { | |
| // Do this on Ctrl-C. | |
| }); | |
| #endif | |
| }); | |
| PRINT_FLUSH("Accepted connection.\n"); | |
| connected_ = true; | |
| return Err(); | |
| } | |
| void SocketConnection::UnregisterAndDestroy() { server_->RemoveConnection(this); } | |
| // SocketServer -------------------------------------------------------------------------------- | |
| void SocketServer::Run(ConnectionConfig config) { | |
| // Wait for one connection. | |
| PRINT_FLUSH("Waiting on port %d for shell connections...\n", config.port); | |
| config_ = config; | |
| connection_monitor_ = debug_ipc::MessageLoop::Current()->WatchFD( | |
| debug_ipc::MessageLoop::WatchMode::kRead, server_socket_.get(), this); | |
| } | |
| Err SocketServer::RunInLoop(ConnectionConfig config, debug_ipc::FileLineFunction from_here, | |
| fit::closure inited_fn) { | |
| std::string init_error_message; | |
| // This loop manages incoming connections, and runs in this thread. | |
| debug_ipc::PlatformMessageLoop server_loop; | |
| if (!server_loop.Init(&init_error_message)) { | |
| return Err(kInit, init_error_message); | |
| } | |
| // Do appropriate init and start accepting connections. | |
| uint16_t port = static_cast<uint16_t>(config.port); | |
| Err err = Init(&port); | |
| if (!err.ok()) { | |
| server_loop.Cleanup(); | |
| return err; | |
| } | |
| config.port = port; | |
| config.message_loop = &server_loop; | |
| Run(std::move(config)); | |
| server_loop.PostTask(from_here, std::move(inited_fn)); | |
| server_loop.Run(); | |
| // Shut down the individual connections associated with the message loop. | |
| auto it = this->connections_.begin(); | |
| while (it != this->connections_.end()) { | |
| it = this->connections_.begin(); | |
| this->RemoveConnection(it->get()); | |
| } | |
| // Shutdown. | |
| // Stop monitoring for new connections (otherwise the destructor complains). | |
| this->connection_monitor_.StopWatching(); | |
| server_loop.Cleanup(); | |
| return Err(); | |
| } | |
| Err SocketServer::Init(uint16_t* port) { | |
| constexpr uint8_t kMaxAttempts = 6; | |
| for (uint8_t attempt = 0; attempt < kMaxAttempts; attempt++) { | |
| server_socket_.reset(socket(AF_INET6, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); | |
| if (!server_socket_.is_valid()) { | |
| return Err(kConnection, "Could not create socket: " + std::string(strerror(errno))); | |
| } | |
| // 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) { | |
| if (attempt != 0 || attempt == kMaxAttempts - 1) { | |
| // Either we tried a designated port in the first iteration, we tried 0 in the first | |
| // iteration and it couldn't give us anything, or we've tried as many times as we could. | |
| std::string msg = "Could not bind socket: "; | |
| msg += strerror(errno); | |
| return Err(kConnection, msg); | |
| } else { | |
| // We're looping - just try again with another port | |
| close(server_socket_.get()); | |
| server_socket_.release(); | |
| *port = 0; | |
| continue; | |
| } | |
| } | |
| if (*port != 0) { | |
| break; | |
| } | |
| // If port wasn't assigned, we want to get one assigned automatically. We passed 0 in, which | |
| // means bind() will give you an unused ephemeral port, which is unbound. | |
| // Figure out which port was granted, close it, and then pretend it's the real port. Because | |
| // this is a bit racy (someone else might try to grab the port), do it up to |attempts| times. | |
| struct sockaddr_in6 addr_out; | |
| socklen_t out_length = sizeof(addr_out); | |
| if ((getsockname(server_socket_.get(), reinterpret_cast<sockaddr*>(&addr), &out_length) < 0) || | |
| out_length != sizeof(addr_out)) { | |
| std::string msg = "Could not get info for socket: "; | |
| msg += strerror(errno); | |
| return Err(kConnection, msg); | |
| } | |
| *port = addr_out.sin6_port; | |
| close(server_socket_.get()); | |
| server_socket_.release(); | |
| } | |
| if (listen(server_socket_.get(), 1) < 0) { | |
| std::string msg = "Could not listen on socket: "; | |
| msg += strerror(errno); | |
| return Err(kConnection, msg); | |
| } | |
| return Err(); | |
| } | |
| void SocketServer::OnFDReady(int fd, bool readable, bool writeable, bool err) { | |
| if (!readable) { | |
| return; | |
| } | |
| // insert() returns a pair <iterator, bool> | |
| auto p = this->connections_.insert(std::make_unique<SocketConnection>(this)); | |
| if (!p.second) { | |
| FX_LOGS(ERROR) << "Internal error"; | |
| return; | |
| } | |
| Err error = p.first->get()->Accept(config_.message_loop, fd); | |
| if (!error.ok()) { | |
| FX_LOGS(INFO) << error.msg; | |
| return; | |
| } | |
| PRINT_FLUSH("Connection established.\n"); | |
| } | |
| // SocketServer -------------------------------------------------------------------------------- | |
| Err Update::SendUpdates() { | |
| for (auto it_entry = std::filesystem::recursive_directory_iterator(path_); | |
| it_entry != std::filesystem::recursive_directory_iterator(); ++it_entry) { | |
| const std::string filename = std::filesystem::absolute(it_entry->path()); | |
| files_.AddFile(filename.c_str()); | |
| } | |
| std::vector<char> dumped_files; | |
| if (files_.DumpFiles(&dumped_files) != 0) { | |
| std::string msg = "Could not dump files: " + std::string(strerror(errno)); | |
| return Err(kWrite, msg); | |
| } | |
| stream_->Write(dumped_files); | |
| return Err(); | |
| } | |
| } // namespace shell::mirror |