| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "host/commands/cvd/server.h" |
| |
| #include <signal.h> |
| #include <unistd.h> |
| |
| #include <atomic> |
| #include <future> |
| #include <map> |
| #include <mutex> |
| #include <optional> |
| #include <thread> |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/strings.h> |
| #include <fmt/core.h> |
| |
| #include "common/libs/fs/shared_buf.h" |
| #include "common/libs/fs/shared_fd.h" |
| #include "common/libs/fs/shared_select.h" |
| #include "common/libs/utils/files.h" |
| #include "common/libs/utils/flag_parser.h" |
| #include "common/libs/utils/result.h" |
| #include "common/libs/utils/shared_fd_flag.h" |
| #include "common/libs/utils/subprocess.h" |
| #include "cvd_server.pb.h" |
| #include "host/commands/cvd/command_sequence.h" |
| #include "host/commands/cvd/common_utils.h" |
| #include "host/commands/cvd/epoll_loop.h" |
| #include "host/commands/cvd/logger.h" |
| #include "host/commands/cvd/request_context.h" |
| #include "host/commands/cvd/run_server.h" |
| #include "host/commands/cvd/selector/selector_constants.h" |
| #include "host/commands/cvd/server_command/cmd_list.h" |
| #include "host/commands/cvd/server_command/display.h" |
| #include "host/commands/cvd/server_command/env.h" |
| #include "host/commands/cvd/server_command/generic.h" |
| #include "host/commands/cvd/server_command/handler_proxy.h" |
| #include "host/commands/cvd/server_command/load_configs.h" |
| #include "host/commands/cvd/server_command/power.h" |
| #include "host/commands/cvd/server_command/reset.h" |
| #include "host/commands/cvd/server_command/snapshot.h" |
| #include "host/commands/cvd/server_command/start.h" |
| #include "host/commands/cvd/server_command/status.h" |
| #include "host/commands/cvd/server_command/subcmd.h" |
| #include "host/commands/cvd/server_constants.h" |
| #include "host/libs/config/known_paths.h" |
| |
| using android::base::ScopeGuard; |
| |
| namespace cuttlefish { |
| |
| static constexpr int kNumThreads = 10; |
| |
| CvdServer::CvdServer(BuildApi& build_api, EpollPool& epoll_pool, |
| InstanceManager& instance_manager, |
| HostToolTargetManager& host_tool_target_manager, |
| ServerLogger& server_logger) |
| : build_api_(build_api), |
| epoll_pool_(epoll_pool), |
| instance_manager_(instance_manager), |
| host_tool_target_manager_(host_tool_target_manager), |
| server_logger_(server_logger), |
| running_(true), |
| optout_(false) { |
| std::scoped_lock lock(threads_mutex_); |
| for (auto i = 0; i < kNumThreads; i++) { |
| threads_.emplace_back([this]() { |
| while (running_) { |
| auto result = epoll_pool_.HandleEvent(); |
| if (!result.ok()) { |
| LOG(ERROR) << "Epoll worker error:\n" |
| << result.error().FormatForEnv(); |
| } |
| } |
| auto wakeup = BestEffortWakeup(); |
| CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); |
| }); |
| } |
| } |
| |
| CvdServer::~CvdServer() { |
| running_ = false; |
| auto wakeup = BestEffortWakeup(); |
| CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); |
| Join(); |
| } |
| |
| Result<void> CvdServer::BestEffortWakeup() { |
| // This attempts to cascade through the responder threads, forcing them |
| // to wake up and see that running_ is false, then exit and wake up |
| // further threads. |
| auto eventfd = SharedFD::Event(); |
| CF_EXPECT(eventfd->IsOpen(), eventfd->StrError()); |
| CF_EXPECT(eventfd->EventfdWrite(1) == 0, eventfd->StrError()); |
| |
| auto cb = [](EpollEvent) -> Result<void> { return {}; }; |
| CF_EXPECT(epoll_pool_.Register(eventfd, EPOLLIN, cb)); |
| return {}; |
| } |
| |
| void CvdServer::Stop() { |
| { |
| std::lock_guard lock(ongoing_requests_mutex_); |
| running_ = false; |
| } |
| while (true) { |
| std::shared_ptr<OngoingRequest> request; |
| { |
| std::lock_guard lock(ongoing_requests_mutex_); |
| if (ongoing_requests_.empty()) { |
| break; |
| } |
| auto it = ongoing_requests_.begin(); |
| request = *it; |
| ongoing_requests_.erase(it); |
| } |
| { |
| std::lock_guard lock(request->mutex); |
| if (request->handler == nullptr) { |
| continue; |
| } |
| request->handler->Interrupt(); |
| } |
| auto wakeup = BestEffortWakeup(); |
| CHECK(wakeup.ok()) << wakeup.error().FormatForEnv(); |
| std::scoped_lock lock(threads_mutex_); |
| for (auto& thread : threads_) { |
| auto current_thread = thread.get_id() == std::this_thread::get_id(); |
| auto matching_thread = thread.get_id() == request->thread_id; |
| if (!current_thread && matching_thread && thread.joinable()) { |
| thread.join(); |
| } |
| } |
| } |
| } |
| |
| void CvdServer::Join() { |
| for (auto& thread : threads_) { |
| if (thread.joinable()) { |
| thread.join(); |
| } |
| } |
| } |
| |
| Result<void> CvdServer::Exec(const ExecParam& exec_param) { |
| CF_EXPECT(server_fd_->IsOpen(), "Server not running"); |
| Stop(); |
| android::base::unique_fd server_dup{server_fd_->UNMANAGED_Dup()}; |
| CF_EXPECT(server_dup.get() >= 0, "dup: \"" << server_fd_->StrError() << "\""); |
| android::base::unique_fd client_dup{ |
| exec_param.carryover_client_fd->UNMANAGED_Dup()}; |
| CF_EXPECT(client_dup.get() >= 0, "dup: \"" << server_fd_->StrError() << "\""); |
| |
| cvd_common::Args argv_str = { |
| kServerExecPath, |
| fmt::format("-{}={}", kInternalServerFd, server_dup.get()), |
| fmt::format("-{}={}", kInternalCarryoverClientFd, client_dup.get()), |
| fmt::format("-{}={}", kInternalAcloudTranslatorOptOut, optout_), |
| fmt::format("-{}={}", kInternalRestartedInProcess, true), |
| }; |
| |
| int in_memory_dup = -1; |
| ScopeGuard exit_action([&in_memory_dup]() { |
| if (in_memory_dup >= 0) { |
| if (close(in_memory_dup) != 0) { |
| LOG(ERROR) << "Failed to close file " << in_memory_dup; |
| } |
| } |
| }); |
| if (exec_param.in_memory_data_fd) { |
| in_memory_dup = exec_param.in_memory_data_fd.value()->UNMANAGED_Dup(); |
| CF_EXPECTF(in_memory_dup >= 0, "dup: \"{}\"", |
| exec_param.in_memory_data_fd.value()->StrError()); |
| argv_str.push_back( |
| ConcatToString("-", kInternalMemoryCarryoverFd, "=", in_memory_dup)); |
| } |
| |
| std::vector<char*> argv_cstr; |
| for (const auto& argv : argv_str) { |
| argv_cstr.emplace_back(strdup(argv.c_str())); |
| } |
| argv_cstr.emplace_back(nullptr); |
| android::base::unique_fd new_exe_dup{exec_param.new_exe->UNMANAGED_Dup()}; |
| CF_EXPECT(new_exe_dup.get() >= 0, |
| "dup: \"" << exec_param.new_exe->StrError() << "\""); |
| |
| if (exec_param.verbose) { |
| LOG(ERROR) << "Server Exec'ing: " << android::base::Join(argv_str, " "); |
| } |
| |
| fexecve(new_exe_dup.get(), argv_cstr.data(), environ); |
| for (const auto& argv : argv_cstr) { |
| free(argv); |
| } |
| return CF_ERR("fexecve failed: \"" << strerror(errno) << "\""); |
| } |
| |
| Result<void> CvdServer::StartServer(SharedFD server_fd) { |
| server_fd_ = server_fd; |
| auto cb = [this](EpollEvent ev) -> Result<void> { |
| CF_EXPECT(AcceptClient(ev)); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(server_fd, EPOLLIN, cb)); |
| return {}; |
| } |
| |
| Result<void> CvdServer::AcceptCarryoverClient(SharedFD client) { |
| auto self_cb = [this](EpollEvent ev) -> Result<void> { |
| CF_EXPECT(HandleMessage(ev)); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(client, EPOLLIN, self_cb)); |
| |
| cvd::Response success_message; |
| success_message.mutable_status()->set_code(cvd::Status::OK); |
| success_message.mutable_command_response(); |
| CF_EXPECT(SendResponse(client, success_message)); |
| return {}; |
| } |
| |
| Result<void> CvdServer::AcceptClient(EpollEvent event) { |
| ScopeGuard stop_on_failure([this] { Stop(); }); |
| |
| CF_EXPECT(event.events & EPOLLIN); |
| auto client_fd = SharedFD::Accept(*event.fd); |
| CF_EXPECT(client_fd->IsOpen(), client_fd->StrError()); |
| auto client_cb = [this](EpollEvent ev) -> Result<void> { |
| CF_EXPECT(HandleMessage(ev)); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(client_fd, EPOLLIN, client_cb)); |
| |
| auto self_cb = [this](EpollEvent ev) -> Result<void> { |
| CF_EXPECT(AcceptClient(ev)); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb)); |
| |
| stop_on_failure.Disable(); |
| return {}; |
| } |
| |
| Result<void> CvdServer::HandleMessage(EpollEvent event) { |
| ScopeGuard abandon_client([this, event] { epoll_pool_.Remove(event.fd); }); |
| |
| if (event.events & EPOLLHUP) { // Client went away. |
| epoll_pool_.Remove(event.fd); |
| return {}; |
| } |
| |
| CF_EXPECT(event.events & EPOLLIN); |
| auto request = CF_EXPECT(GetRequest(event.fd)); |
| if (!request) { // End-of-file / client went away. |
| epoll_pool_.Remove(event.fd); |
| return {}; |
| } |
| const auto verbosity = request->Message().verbosity(); |
| const auto encoded_verbosity = EncodeVerbosity(verbosity); |
| auto logger = |
| encoded_verbosity.ok() |
| ? server_logger_.LogThreadToFd(request->Err(), *encoded_verbosity) |
| : server_logger_.LogThreadToFd(request->Err()); |
| auto response = HandleRequest(*request, event.fd); |
| if (!response.ok()) { |
| cvd::Response failure_message; |
| failure_message.mutable_status()->set_code(cvd::Status::INTERNAL); |
| failure_message.mutable_status()->set_message( |
| response.error().FormatForEnv()); |
| CF_EXPECT(SendResponse(event.fd, failure_message)); |
| return {}; // Error already sent to the client, don't repeat on the server |
| } |
| CF_EXPECT(SendResponse(event.fd, *response)); |
| |
| auto self_cb = [this, err = request->Err()](EpollEvent ev) -> Result<void> { |
| CF_EXPECT(HandleMessage(ev)); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(event.fd, EPOLLIN, self_cb)); |
| |
| abandon_client.Disable(); |
| return {}; |
| } |
| |
| static Result<android::base::LogSeverity> Verbosity( |
| const RequestWithStdio& request, const std::string& default_val) { |
| if (request.Message().contents_case() != |
| cvd::Request::ContentsCase::kCommandRequest) { |
| return default_val.empty() ? kCvdDefaultVerbosity |
| : CF_EXPECT(EncodeVerbosity(default_val)); |
| } |
| const auto& selector_opts = |
| request.Message().command_request().selector_opts(); |
| auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); |
| auto verbosity_flag = CF_EXPECT(selector::SelectorFlags::New()) |
| .FlagsAsCollection() |
| .GetFlag(selector::SelectorFlags::kVerbosity); |
| auto verbosity_opt = |
| CF_EXPECT(verbosity_flag->FilterFlag<std::string>(selector_args)); |
| auto ret_val = verbosity_opt.value_or(default_val); |
| if (ret_val.empty()) { |
| const auto severity_in_string = |
| CF_EXPECT(VerbosityToString(kCvdDefaultVerbosity)); |
| LOG(DEBUG) << "Verbosity level is not given, so using the default value: " |
| << severity_in_string << " is used"; |
| return kCvdDefaultVerbosity; |
| } |
| return CF_EXPECT(EncodeVerbosity(ret_val), |
| "Invalid verbosity level : \"" << ret_val << "\""); |
| } |
| |
| // convert HOME, ANDROID_HOST_OUT, ANDROID_SOONG_HOST_OUT |
| // and ANDROID_PRODUCT_OUT into absolute paths if any. |
| static Result<RequestWithStdio> ConvertDirPathToAbsolute( |
| const RequestWithStdio& request) { |
| if (request.Message().contents_case() != |
| cvd::Request::ContentsCase::kCommandRequest) { |
| return request; |
| } |
| if (request.Message().command_request().env().empty()) { |
| return request; |
| } |
| auto envs = |
| cvd_common::ConvertToEnvs(request.Message().command_request().env()); |
| std::unordered_set<std::string> interested_envs{ |
| kAndroidHostOut, kAndroidSoongHostOut, "HOME", kAndroidProductOut}; |
| const auto& current_dir = |
| request.Message().command_request().working_directory(); |
| |
| // make sure that "~" is not included |
| for (const auto& key : interested_envs) { |
| if (!Contains(envs, key)) { |
| continue; |
| } |
| const auto& dir = envs.at(key); |
| CF_EXPECT(dir != "~" && !android::base::StartsWith(dir, "~/"), |
| "The " << key << " directory should not start with ~"); |
| } |
| |
| for (const auto& key : interested_envs) { |
| if (!Contains(envs, key)) { |
| continue; |
| } |
| const auto dir = envs.at(key); |
| envs[key] = |
| CF_EXPECT(EmulateAbsolutePath({.current_working_dir = current_dir, |
| .home_dir = std::nullopt, // unused |
| .path_to_convert = dir, |
| .follow_symlink = false})); |
| } |
| |
| auto cmd_args = |
| cvd_common::ConvertToArgs(request.Message().command_request().args()); |
| auto selector_args = cvd_common::ConvertToArgs( |
| request.Message().command_request().selector_opts().args()); |
| RequestWithStdio new_request( |
| request.Client(), |
| MakeRequest({.cmd_args = std::move(cmd_args), |
| .env = std::move(envs), |
| .selector_args = std::move(selector_args), |
| .working_dir = current_dir}, |
| request.Message().command_request().wait_behavior()), |
| request.FileDescriptors(), request.Credentials()); |
| return new_request; |
| } |
| |
| static Result<void> VerifyUser(const RequestWithStdio& request) { |
| CF_EXPECT(request.Credentials(), |
| "ucred is not available while it is necessary."); |
| const uid_t client_uid = request.Credentials()->uid; |
| CF_EXPECT_EQ(client_uid, getuid(), "Cvd server process is one per user."); |
| return {}; |
| } |
| |
| Result<cvd::Response> CvdServer::HandleRequest(RequestWithStdio orig_request, |
| SharedFD client) { |
| CF_EXPECT(VerifyUser(orig_request)); |
| auto request = CF_EXPECT(ConvertDirPathToAbsolute(orig_request)); |
| const auto verbosity = |
| CF_EXPECT(Verbosity(request, request.Message().verbosity())); |
| server_logger_.SetSeverity(verbosity); |
| |
| RequestContext context(*this, instance_manager_, build_api_, |
| host_tool_target_manager_, optout_); |
| |
| // Even if the interrupt callback outlives the request handler, it'll only |
| // hold on to this struct which will be cleaned out when the request handler |
| // exits. |
| auto shared = std::make_shared<OngoingRequest>(); |
| shared->handler = CF_EXPECT(context.Handler(request)); |
| shared->thread_id = std::this_thread::get_id(); |
| |
| { |
| std::lock_guard lock(ongoing_requests_mutex_); |
| if (running_) { |
| ongoing_requests_.insert(shared); |
| } else { |
| // We're executing concurrently with a Stop() call. |
| return {}; |
| } |
| } |
| ScopeGuard remove_ongoing_request([this, shared] { |
| std::lock_guard lock(ongoing_requests_mutex_); |
| ongoing_requests_.erase(shared); |
| }); |
| |
| auto interrupt_cb = [this, shared, verbosity, |
| err = request.Err()](EpollEvent) -> Result<void> { |
| auto logger = server_logger_.LogThreadToFd(err, verbosity); |
| std::lock_guard lock(shared->mutex); |
| CF_EXPECT(shared->handler != nullptr); |
| CF_EXPECT(shared->handler->Interrupt()); |
| return {}; |
| }; |
| CF_EXPECT(epoll_pool_.Register(client, EPOLLHUP, interrupt_cb)); |
| |
| auto response = CF_EXPECT(shared->handler->Handle(request)); |
| { |
| std::lock_guard lock(shared->mutex); |
| shared->handler = nullptr; |
| } |
| CF_EXPECT(epoll_pool_.Remove(client)); // Delete interrupt handler |
| |
| return response; |
| } |
| |
| Result<void> CvdServer::InstanceDbFromJson(const std::string& json_string) { |
| const uid_t uid = getuid(); |
| auto json = CF_EXPECT(ParseJson(json_string)); |
| CF_EXPECT(instance_manager_.LoadFromJson(uid, json)); |
| return {}; |
| } |
| |
| Result<int> CvdServerMain(ServerMainParam&& param) { |
| SetMinimumVerbosity(android::base::VERBOSE); |
| |
| LOG(INFO) << "Starting server"; |
| |
| if (!param.restarted_in_process) { |
| LOG(INFO) << "Server is being daemonized..."; |
| CF_EXPECT(daemon(0, 0) != -1, strerror(errno)); |
| } |
| |
| signal(SIGPIPE, SIG_IGN); |
| |
| SharedFD server_fd = std::move(param.internal_server_fd); |
| CF_EXPECT(server_fd->IsOpen(), "Did not receive a valid cvd_server fd"); |
| |
| std::unique_ptr<ServerLogger> server_logger = std::move(param.server_logger); |
| BuildApi build_api; |
| EpollPool epoll_pool; |
| auto host_tool_target_manager = NewHostToolTargetManager(); |
| InstanceLockFileManager lock_manager; |
| InstanceManager instance_manager(lock_manager, *host_tool_target_manager); |
| CvdServer server(build_api, epoll_pool, instance_manager, |
| *host_tool_target_manager, *server_logger); |
| |
| std::optional<SharedFD> memory_carryover_fd = |
| std::move(param.memory_carryover_fd); |
| if (memory_carryover_fd) { |
| const std::string json_string = |
| CF_EXPECT(ReadAllFromMemFd(*memory_carryover_fd)); |
| CF_EXPECT(server.InstanceDbFromJson(json_string), |
| "Failed to load from: " << json_string); |
| } |
| if (param.acloud_translator_optout) { |
| server.optout_ = param.acloud_translator_optout.value(); |
| } |
| server.StartServer(server_fd); |
| |
| // The carryover_client wouldn't be available after AcceptCarryoverClient() |
| if (param.carryover_client_fd->IsOpen()) { |
| // release scoped_logger for this thread inside AcceptCarryoverClient() |
| CF_EXPECT( |
| server.AcceptCarryoverClient(std::move(param.carryover_client_fd))); |
| } |
| server.Join(); |
| |
| return 0; |
| } |
| |
| Result<std::string> ReadAllFromMemFd(const SharedFD& mem_fd) { |
| const auto n_message_size = mem_fd->LSeek(0, SEEK_END); |
| CF_EXPECT_NE(n_message_size, -1, "LSeek on the memory file failed."); |
| std::vector<char> buffer(n_message_size); |
| CF_EXPECT_EQ(mem_fd->LSeek(0, SEEK_SET), 0, mem_fd->StrError()); |
| auto n_read = ReadExact(mem_fd, buffer.data(), n_message_size); |
| CF_EXPECT(n_read == n_message_size, |
| "Expected to read " << n_message_size << " bytes but actually read " |
| << n_read << " bytes."); |
| std::string message(buffer.begin(), buffer.end()); |
| return message; |
| } |
| |
| } // namespace cuttlefish |