1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "host/commands/cvd/server_command/utils.h"
18
19 #include "common/libs/fs/shared_buf.h"
20 #include "common/libs/utils/contains.h"
21 #include "common/libs/utils/files.h"
22 #include "common/libs/utils/flag_parser.h"
23 #include "common/libs/utils/users.h"
24 #include "host/commands/cvd/instance_manager.h"
25 #include "host/commands/cvd/server.h"
26 #include "host/libs/config/cuttlefish_config.h"
27
28 namespace cuttlefish {
29
ParseInvocation(const cvd::Request & request)30 CommandInvocation ParseInvocation(const cvd::Request& request) {
31 CommandInvocation invocation;
32 if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) {
33 return invocation;
34 }
35 if (request.command_request().args_size() == 0) {
36 return invocation;
37 }
38 for (const std::string& arg : request.command_request().args()) {
39 invocation.arguments.push_back(arg);
40 }
41 invocation.arguments[0] = cpp_basename(invocation.arguments[0]);
42 if (invocation.arguments[0] == "cvd") {
43 invocation.command = invocation.arguments[1];
44 invocation.arguments.erase(invocation.arguments.begin());
45 invocation.arguments.erase(invocation.arguments.begin());
46 } else {
47 invocation.command = invocation.arguments[0];
48 invocation.arguments.erase(invocation.arguments.begin());
49 }
50 return invocation;
51 }
52
VerifyPrecondition(const RequestWithStdio & request)53 Result<void> VerifyPrecondition(const RequestWithStdio& request) {
54 CF_EXPECT(
55 Contains(request.Message().command_request().env(), kAndroidHostOut),
56 "ANDROID_HOST_OUT in client environment is invalid.");
57 return {};
58 }
59
ResponseFromSiginfo(siginfo_t infop)60 cuttlefish::cvd::Response ResponseFromSiginfo(siginfo_t infop) {
61 cvd::Response response;
62 response.mutable_command_response(); // set oneof field
63 auto& status = *response.mutable_status();
64 if (infop.si_code == CLD_EXITED && infop.si_status == 0) {
65 status.set_code(cvd::Status::OK);
66 return response;
67 }
68
69 status.set_code(cvd::Status::INTERNAL);
70 std::string status_code_str = std::to_string(infop.si_status);
71 if (infop.si_code == CLD_EXITED) {
72 status.set_message("Exited with code " + status_code_str);
73 } else if (infop.si_code == CLD_KILLED) {
74 status.set_message("Exited with signal " + status_code_str);
75 } else {
76 status.set_message("Quit with code " + status_code_str);
77 }
78 return response;
79 }
80
ConstructCommand(const ConstructCommandParam & param)81 Result<Command> ConstructCommand(const ConstructCommandParam& param) {
82 Command command(param.command_name);
83 command.SetExecutable(param.bin_path);
84 for (const std::string& arg : param.args) {
85 command.AddParameter(arg);
86 }
87 // Set CuttlefishConfig path based on assembly dir,
88 // used by subcommands when locating the CuttlefishConfig.
89 if (param.envs.count(cuttlefish::kCuttlefishConfigEnvVarName) == 0) {
90 auto config_path = InstanceManager::GetCuttlefishConfigPath(param.home);
91 if (config_path.ok()) {
92 command.AddEnvironmentVariable(cuttlefish::kCuttlefishConfigEnvVarName,
93 *config_path);
94 }
95 }
96 for (auto& it : param.envs) {
97 command.UnsetFromEnvironment(it.first);
98 command.AddEnvironmentVariable(it.first, it.second);
99 }
100 // Redirect stdin, stdout, stderr back to the cvd client
101 command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, param.in);
102 command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, param.out);
103 command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, param.err);
104
105 if (!param.working_dir.empty()) {
106 auto fd =
107 SharedFD::Open(param.working_dir, O_RDONLY | O_PATH | O_DIRECTORY);
108 CF_EXPECT(fd->IsOpen(), "Couldn't open \"" << param.working_dir
109 << "\": " << fd->StrError());
110 command.SetWorkingDirectory(fd);
111 }
112 return {std::move(command)};
113 }
114
ConstructCvdHelpCommand(const std::string & bin_file,cvd_common::Envs envs,const std::vector<std::string> & subcmd_args,const RequestWithStdio & request)115 Result<Command> ConstructCvdHelpCommand(
116 const std::string& bin_file, cvd_common::Envs envs,
117 const std::vector<std::string>& subcmd_args,
118 const RequestWithStdio& request) {
119 const auto host_artifacts_path = envs.at("ANDROID_HOST_OUT");
120 const auto bin_path = host_artifacts_path + "/bin/" + bin_file;
121 auto client_pwd = request.Message().command_request().working_directory();
122 const auto home = (Contains(envs, "HOME") ? envs.at("HOME") : client_pwd);
123 cvd_common::Envs envs_copy{envs};
124 envs_copy["HOME"] = AbsolutePath(home);
125 envs[kAndroidSoongHostOut] = envs.at(kAndroidHostOut);
126 ConstructCommandParam construct_cmd_param{.bin_path = bin_path,
127 .home = home,
128 .args = subcmd_args,
129 .envs = std::move(envs_copy),
130 .working_dir = client_pwd,
131 .command_name = bin_file,
132 .in = request.In(),
133 .out = request.Out(),
134 .err = request.Err()};
135 Command help_command = CF_EXPECT(ConstructCommand(construct_cmd_param));
136 return help_command;
137 }
138
ConstructCvdGenericNonHelpCommand(const ConstructNonHelpForm & request_form,const RequestWithStdio & request)139 Result<Command> ConstructCvdGenericNonHelpCommand(
140 const ConstructNonHelpForm& request_form, const RequestWithStdio& request) {
141 cvd_common::Envs envs{request_form.envs};
142 envs["HOME"] = request_form.home;
143 envs[kAndroidHostOut] = request_form.android_host_out;
144 envs[kAndroidSoongHostOut] = request_form.android_host_out;
145 const auto bin_path = ConcatToString(request_form.android_host_out, "/bin/",
146 request_form.bin_file);
147
148 if (request_form.verbose) {
149 std::stringstream verbose_stream;
150 verbose_stream << "HOME=" << request_form.home << " ";
151 verbose_stream << kAndroidHostOut << "=" << envs.at(kAndroidHostOut) << " "
152 << kAndroidSoongHostOut << "="
153 << envs.at(kAndroidSoongHostOut) << " ";
154 verbose_stream << bin_path << "\\" << std::endl;
155 for (const auto& cmd_arg : request_form.cmd_args) {
156 verbose_stream << cmd_arg << " ";
157 }
158 if (!request_form.cmd_args.empty()) {
159 // remove trailing " ", and add a new line
160 verbose_stream.seekp(-1, std::ios_base::end);
161 verbose_stream << std::endl;
162 }
163 WriteAll(request.Err(), verbose_stream.str());
164 }
165 ConstructCommandParam construct_cmd_param{
166 .bin_path = bin_path,
167 .home = request_form.home,
168 .args = request_form.cmd_args,
169 .envs = envs,
170 .working_dir = request.Message().command_request().working_directory(),
171 .command_name = request_form.bin_file,
172 .in = request.In(),
173 .out = request.Out(),
174 .err = request.Err()};
175 return CF_EXPECT(ConstructCommand(construct_cmd_param));
176 }
177
178 /*
179 * From external/gflags/src, commit:
180 * 061f68cd158fa658ec0b9b2b989ed55764870047
181 *
182 */
183 constexpr static std::array help_bool_opts{
184 "help", "helpfull", "helpshort", "helppackage", "helpxml", "version"};
185 constexpr static std::array help_str_opts{
186 "helpon",
187 "helpmatch",
188 };
189
IsHelpSubcmd(const std::vector<std::string> & args)190 bool IsHelpSubcmd(const std::vector<std::string>& args) {
191 std::vector<std::string> copied_args(args);
192 std::vector<Flag> flags;
193 flags.reserve(help_bool_opts.size() + help_str_opts.size());
194 bool bool_value_placeholder = false;
195 std::string str_value_placeholder;
196 for (const auto bool_opt : help_bool_opts) {
197 flags.emplace_back(GflagsCompatFlag(bool_opt, bool_value_placeholder));
198 }
199 for (const auto str_opt : help_str_opts) {
200 flags.emplace_back(GflagsCompatFlag(str_opt, str_value_placeholder));
201 }
202 ParseFlags(flags, copied_args);
203 // if there was any match, some in copied_args were consumed.
204 return (args.size() != copied_args.size());
205 }
206
207 } // namespace cuttlefish
208