/* * Copyright (C) 2022 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 #include #include #include "cvd_server.pb.h" #include "common/libs/fs/shared_buf.h" #include "common/libs/fs/shared_fd.h" #include "common/libs/utils/flag_parser.h" #include "common/libs/utils/result.h" #include "common/libs/utils/subprocess.h" #include "host/commands/cvd/command_sequence.h" #include "host/commands/cvd/instance_lock.h" #include "host/commands/cvd/server_client.h" namespace cuttlefish { namespace { struct ConvertedAcloudCreateCommand { InstanceLockFile lock; std::vector requests; }; /** * Split a string into arguments based on shell tokenization rules. * * This behaves like `shlex.split` from python where arguments are separated * based on whitespace, but quoting and quote escaping is respected. This * function effectively removes one level of quoting from its inputs while * making the split. */ Result> BashTokenize(const std::string& str) { Command command("bash"); command.AddParameter("-c"); command.AddParameter("printf '%s\n' ", str); std::string stdout; std::string stderr; auto ret = RunWithManagedStdio(std::move(command), nullptr, &stdout, &stderr); CF_EXPECT(ret == 0, "printf fail \"" << stdout << "\", \"" << stderr << "\""); return android::base::Split(stdout, "\n"); } class ConvertAcloudCreateCommand { public: INJECT(ConvertAcloudCreateCommand(InstanceLockFileManager& lock_file_manager)) : lock_file_manager_(lock_file_manager) {} Result Convert( const RequestWithStdio& request) { auto arguments = ParseInvocation(request.Message()).arguments; CF_EXPECT(arguments.size() > 0); CF_EXPECT(arguments[0] == "create"); arguments.erase(arguments.begin()); const auto& request_command = request.Message().command_request(); std::vector flags; bool local_instance_set; std::optional local_instance; auto local_instance_flag = Flag(); local_instance_flag.Alias( {FlagAliasMode::kFlagConsumesArbitrary, "--local-instance"}); local_instance_flag.Setter([&local_instance_set, &local_instance](const FlagMatch& m) { local_instance_set = true; if (m.value != "" && local_instance) { LOG(ERROR) << "Instance number already set, was \"" << *local_instance << "\", now set to \"" << m.value << "\""; return false; } else if (m.value != "" && !local_instance) { local_instance = std::stoi(m.value); } return true; }); flags.emplace_back(local_instance_flag); bool verbose = false; flags.emplace_back(Flag() .Alias({FlagAliasMode::kFlagExact, "-v"}) .Alias({FlagAliasMode::kFlagExact, "-vv"}) .Alias({FlagAliasMode::kFlagExact, "--verbose"}) .Setter([&verbose](const FlagMatch&) { verbose = true; return true; })); std::optional branch; flags.emplace_back( Flag() .Alias({FlagAliasMode::kFlagConsumesFollowing, "--branch"}) .Setter([&branch](const FlagMatch& m) { branch = m.value; return true; })); bool local_image; flags.emplace_back( Flag() .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--local-image"}) .Setter([&local_image](const FlagMatch& m) { local_image = true; return m.value == ""; })); std::optional build_id; flags.emplace_back( Flag() .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-id"}) .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_id"}) .Setter([&build_id](const FlagMatch& m) { build_id = m.value; return true; })); std::optional build_target; flags.emplace_back( Flag() .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-target"}) .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_target"}) .Setter([&build_target](const FlagMatch& m) { build_target = m.value; return true; })); std::optional launch_args; flags.emplace_back( Flag() .Alias({FlagAliasMode::kFlagConsumesFollowing, "--launch-args"}) .Setter([&launch_args](const FlagMatch& m) { launch_args = m.value; return true; })); CF_EXPECT(ParseFlags(flags, arguments)); CF_EXPECT(arguments.size() == 0, "Unrecognized arguments:'" << android::base::Join(arguments, "', '") << "'"); CF_EXPECT(local_instance_set == true, "Only '--local-instance' is supported"); std::optional lock; if (local_instance.has_value()) { // TODO(schuffelen): Block here if it can be interruptible lock = CF_EXPECT(lock_file_manager_.TryAcquireLock(*local_instance)); } else { lock = CF_EXPECT(lock_file_manager_.TryAcquireUnusedLock()); } CF_EXPECT(lock.has_value(), "Could not acquire instance lock"); CF_EXPECT(CF_EXPECT(lock->Status()) == InUseState::kNotInUse); auto dir = TempDir() + "/acloud_cvd_temp/local-instance-" + std::to_string(lock->Instance()); static constexpr char kAndroidHostOut[] = "ANDROID_HOST_OUT"; auto host_artifacts_path = request_command.env().find(kAndroidHostOut); CF_EXPECT(host_artifacts_path != request_command.env().end(), "Missing " << kAndroidHostOut); std::vector request_protos; if (local_image) { cvd::Request& mkdir_request = request_protos.emplace_back(); auto& mkdir_command = *mkdir_request.mutable_command_request(); mkdir_command.add_args("cvd"); mkdir_command.add_args("mkdir"); mkdir_command.add_args("-p"); mkdir_command.add_args(dir); auto& mkdir_env = *mkdir_command.mutable_env(); mkdir_env[kAndroidHostOut] = host_artifacts_path->second; *mkdir_command.mutable_working_directory() = dir; } else { cvd::Request& fetch_request = request_protos.emplace_back(); auto& fetch_command = *fetch_request.mutable_command_request(); fetch_command.add_args("cvd"); fetch_command.add_args("fetch"); fetch_command.add_args("--directory"); fetch_command.add_args(dir); if (branch || build_id || build_target) { fetch_command.add_args("--default_build"); auto target = build_target ? "/" + *build_target : ""; auto build = build_id.value_or(branch.value_or("aosp-master")); fetch_command.add_args(build + target); } *fetch_command.mutable_working_directory() = dir; auto& fetch_env = *fetch_command.mutable_env(); fetch_env[kAndroidHostOut] = host_artifacts_path->second; } cvd::Request& start_request = request_protos.emplace_back(); auto& start_command = *start_request.mutable_command_request(); start_command.add_args("cvd"); start_command.add_args("start"); start_command.add_args("--daemon"); start_command.add_args("--undefok"); start_command.add_args("report_anonymous_usage_stats"); start_command.add_args("--report_anonymous_usage_stats"); start_command.add_args("y"); if (launch_args) { for (const auto& arg : CF_EXPECT(BashTokenize(*launch_args))) { start_command.add_args(arg); } } static constexpr char kAndroidProductOut[] = "ANDROID_PRODUCT_OUT"; auto& start_env = *start_command.mutable_env(); if (local_image) { start_env[kAndroidHostOut] = host_artifacts_path->second; auto product_out = request_command.env().find(kAndroidProductOut); CF_EXPECT(product_out != request_command.env().end(), "Missing " << kAndroidProductOut); start_env[kAndroidProductOut] = product_out->second; } else { start_env[kAndroidHostOut] = dir; start_env[kAndroidProductOut] = dir; } start_env["CUTTLEFISH_INSTANCE"] = std::to_string(lock->Instance()); start_env["HOME"] = dir; *start_command.mutable_working_directory() = dir; std::vector fds; if (verbose) { fds = request.FileDescriptors(); } else { auto dev_null = SharedFD::Open("/dev/null", O_RDWR); CF_EXPECT(dev_null->IsOpen(), dev_null->StrError()); fds = {dev_null, dev_null, dev_null}; } ConvertedAcloudCreateCommand ret = { .lock = {std::move(*lock)}, }; for (auto& request_proto : request_protos) { ret.requests.emplace_back(request_proto, fds, request.Credentials()); } return ret; } private: InstanceLockFileManager& lock_file_manager_; }; class TryAcloudCreateCommand : public CvdServerHandler { public: INJECT(TryAcloudCreateCommand(ConvertAcloudCreateCommand& converter)) : converter_(converter) {} ~TryAcloudCreateCommand() = default; Result CanHandle(const RequestWithStdio& request) const override { auto invocation = ParseInvocation(request.Message()); return invocation.command == "try-acloud" && invocation.arguments.size() >= 1 && invocation.arguments[0] == "create"; } Result Handle(const RequestWithStdio& request) override { CF_EXPECT(converter_.Convert(request)); return CF_ERR("Unreleased"); } Result Interrupt() override { return CF_ERR("Can't be interrupted."); } private: ConvertAcloudCreateCommand& converter_; }; class AcloudCreateCommand : public CvdServerHandler { public: INJECT(AcloudCreateCommand(CommandSequenceExecutor& executor, ConvertAcloudCreateCommand& converter)) : executor_(executor), converter_(converter) {} ~AcloudCreateCommand() = default; Result CanHandle(const RequestWithStdio& request) const override { auto invocation = ParseInvocation(request.Message()); return invocation.command == "acloud" && invocation.arguments.size() >= 1 && invocation.arguments[0] == "create"; } Result Handle(const RequestWithStdio& request) override { std::unique_lock interrupt_lock(interrupt_mutex_); if (interrupted_) { return CF_ERR("Interrupted"); } CF_EXPECT(CanHandle(request)); auto converted = CF_EXPECT(converter_.Convert(request)); interrupt_lock.unlock(); CF_EXPECT(executor_.Execute(converted.requests, request.Err())); CF_EXPECT(converted.lock.Status(InUseState::kInUse)); cvd::Response response; response.mutable_command_response(); return response; } Result Interrupt() override { std::scoped_lock interrupt_lock(interrupt_mutex_); interrupted_ = true; CF_EXPECT(executor_.Interrupt()); return {}; } private: CommandSequenceExecutor& executor_; ConvertAcloudCreateCommand& converter_; std::mutex interrupt_mutex_; bool interrupted_ = false; }; } // namespace fruit::Component> AcloudCommandComponent() { return fruit::createComponent() .addMultibinding() .addMultibinding(); } } // namespace cuttlefish