| /* |
| * 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_command_start_impl.h" |
| |
| #include <sys/types.h> |
| |
| #include <cstdint> |
| #include <cstdlib> |
| |
| #include <android-base/parseint.h> |
| #include <android-base/strings.h> |
| |
| #include "common/libs/fs/shared_buf.h" |
| #include "common/libs/fs/shared_fd.h" |
| #include "common/libs/utils/contains.h" |
| #include "common/libs/utils/subprocess.h" |
| #include "host/libs/config/cuttlefish_config.h" |
| |
| namespace cuttlefish { |
| namespace cvd_cmd_impl { |
| |
| Result<bool> CvdStartCommandHandler::CanHandle( |
| const RequestWithStdio& request) const { |
| auto invocation = ParseInvocation(request.Message()); |
| return Contains(command_to_binary_map_, invocation.command); |
| } |
| |
| Result<Command> CvdStartCommandHandler::ConstructCvdNonHelpCommand( |
| const std::string& bin_file, const selector::GroupCreationInfo& group_info, |
| const RequestWithStdio& request) { |
| const auto bin_path = group_info.host_artifacts_path + "/bin/" + bin_file; |
| CF_EXPECT(!group_info.home.empty()); |
| ConstructCommandParam construct_cmd_param{ |
| .bin_path = bin_path, |
| .home = group_info.home, |
| .args = group_info.args, |
| .envs = group_info.envs, |
| .working_dir = request.Message().command_request().working_directory(), |
| .command_name = bin_file, |
| .in = request.In(), |
| .out = request.Out(), |
| .err = request.Err()}; |
| Command non_help_command = CF_EXPECT(ConstructCommand(construct_cmd_param)); |
| return non_help_command; |
| } |
| |
| // call this only if !is_help |
| Result<selector::GroupCreationInfo> |
| CvdStartCommandHandler::GetGroupCreationInfo( |
| const std::string& subcmd, const std::vector<std::string>& subcmd_args, |
| const cvd_common::Envs& envs, const RequestWithStdio& request) { |
| using CreationAnalyzerParam = |
| selector::CreationAnalyzer::CreationAnalyzerParam; |
| const auto& selector_opts = |
| request.Message().command_request().selector_opts(); |
| const auto selector_args = cvd_common::ConvertToArgs(selector_opts.args()); |
| CreationAnalyzerParam analyzer_param{ |
| .cmd_args = subcmd_args, .envs = envs, .selector_args = selector_args}; |
| auto cred = CF_EXPECT(request.Credentials()); |
| auto group_creation_info = |
| CF_EXPECT(instance_manager_.Analyze(subcmd, analyzer_param, cred)); |
| return group_creation_info; |
| } |
| |
| Result<cvd::Response> CvdStartCommandHandler::Handle( |
| const RequestWithStdio& request) { |
| std::unique_lock interrupt_lock(interruptible_); |
| if (interrupted_) { |
| return CF_ERR("Interrupted"); |
| } |
| CF_EXPECT(CanHandle(request)); |
| |
| cvd::Response response; |
| response.mutable_command_response(); |
| |
| auto [meets_precondition, error_message] = VerifyPrecondition(request); |
| if (!meets_precondition) { |
| response.mutable_status()->set_code(cvd::Status::FAILED_PRECONDITION); |
| response.mutable_status()->set_message(error_message); |
| return response; |
| } |
| |
| const uid_t uid = request.Credentials()->uid; |
| cvd_common::Envs envs = |
| cvd_common::ConvertToEnvs(request.Message().command_request().env()); |
| if (Contains(envs, "HOME")) { |
| // As the end-user may override HOME, this could be a relative path |
| // to client's pwd, or may include "~" which is the client's actual |
| // home directory. |
| auto client_pwd = request.Message().command_request().working_directory(); |
| envs["HOME"] = CF_EXPECT(ClientAbsolutePath(envs["HOME"], uid, client_pwd)); |
| } |
| |
| // update DB if not help |
| // collect group creation infos |
| auto [subcmd, subcmd_args] = ParseInvocation(request.Message()); |
| CF_EXPECT(subcmd == "start", "subcmd should be start but is " << subcmd); |
| const bool is_help = HasHelpOpts(subcmd_args); |
| const auto bin = command_to_binary_map_.at(subcmd); |
| |
| std::optional<selector::GroupCreationInfo> group_creation_info; |
| if (!is_help) { |
| group_creation_info = |
| CF_EXPECT(GetGroupCreationInfo(subcmd, subcmd_args, envs, request)); |
| CF_EXPECT(UpdateInstanceDatabase(uid, *group_creation_info)); |
| } |
| |
| Command command = |
| is_help |
| ? CF_EXPECT(ConstructCvdHelpCommand(bin, envs, subcmd_args, request)) |
| : CF_EXPECT( |
| ConstructCvdNonHelpCommand(bin, *group_creation_info, request)); |
| |
| const bool should_wait = |
| (request.Message().command_request().wait_behavior() != |
| cvd::WAIT_BEHAVIOR_START); |
| FireCommand(std::move(command), should_wait); |
| if (!should_wait) { |
| response.mutable_status()->set_code(cvd::Status::OK); |
| return response; |
| } |
| interrupt_lock.unlock(); |
| |
| auto infop = CF_EXPECT(subprocess_waiter_.Wait()); |
| if (infop.si_code != CLD_EXITED || infop.si_status != EXIT_SUCCESS) { |
| if (!is_help) { |
| instance_manager_.RemoveInstanceGroup(uid, group_creation_info->home); |
| } |
| } |
| return ResponseFromSiginfo(infop); |
| } |
| |
| Result<void> CvdStartCommandHandler::Interrupt() { |
| std::scoped_lock interrupt_lock(interruptible_); |
| interrupted_ = true; |
| CF_EXPECT(subprocess_waiter_.Interrupt()); |
| return {}; |
| } |
| |
| Result<void> CvdStartCommandHandler::UpdateInstanceDatabase( |
| const uid_t uid, const selector::GroupCreationInfo& group_creation_info) { |
| CF_EXPECT(instance_manager_.SetInstanceGroup(uid, group_creation_info), |
| group_creation_info.home |
| << " is already taken so can't create new instance."); |
| return {}; |
| } |
| |
| Result<void> CvdStartCommandHandler::FireCommand(Command&& command, |
| const bool wait) { |
| SubprocessOptions options; |
| if (!wait) { |
| options.ExitWithParent(false); |
| } |
| CF_EXPECT(subprocess_waiter_.Setup(command.Start(options))); |
| return {}; |
| } |
| |
| bool CvdStartCommandHandler::HasHelpOpts( |
| const std::vector<std::string>& args) const { |
| return IsHelpSubcmd(args); |
| } |
| |
| std::vector<std::string> CvdStartCommandHandler::CmdList() const { |
| std::vector<std::string> subcmd_list; |
| subcmd_list.reserve(command_to_binary_map_.size()); |
| for (const auto& [cmd, _] : command_to_binary_map_) { |
| subcmd_list.emplace_back(cmd); |
| } |
| return subcmd_list; |
| } |
| |
| const std::map<std::string, std::string> |
| CvdStartCommandHandler::command_to_binary_map_ = { |
| {"start", kStartBin}, |
| {"launch_cvd", kStartBin}, |
| }; |
| |
| } // namespace cvd_cmd_impl |
| } // namespace cuttlefish |