| /* |
| * 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 <optional> |
| #include <vector> |
| |
| #include <android-base/strings.h> |
| |
| #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<RequestWithStdio> 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<std::vector<std::string>> 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<ConvertedAcloudCreateCommand> 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<Flag> flags; |
| bool local_instance_set; |
| std::optional<int> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<InstanceLockFile> 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<cvd::Request> 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<SharedFD> 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<bool> 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<cvd::Response> Handle(const RequestWithStdio& request) override { |
| CF_EXPECT(converter_.Convert(request)); |
| return CF_ERR("Unreleased"); |
| } |
| Result<void> 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<bool> CanHandle(const RequestWithStdio& request) const override { |
| auto invocation = ParseInvocation(request.Message()); |
| return invocation.command == "acloud" && invocation.arguments.size() >= 1 && |
| invocation.arguments[0] == "create"; |
| } |
| Result<cvd::Response> 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<void> 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<fruit::Required<CvdCommandHandler>> AcloudCommandComponent() { |
| return fruit::createComponent() |
| .addMultibinding<CvdServerHandler, AcloudCreateCommand>() |
| .addMultibinding<CvdServerHandler, TryAcloudCreateCommand>(); |
| } |
| |
| } // namespace cuttlefish |