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 #include "host/commands/cvd/server.h"
17
18 #include <optional>
19 #include <vector>
20
21 #include <android-base/strings.h>
22
23 #include "cvd_server.pb.h"
24
25 #include "common/libs/fs/shared_buf.h"
26 #include "common/libs/fs/shared_fd.h"
27 #include "common/libs/utils/flag_parser.h"
28 #include "common/libs/utils/result.h"
29 #include "common/libs/utils/subprocess.h"
30 #include "host/commands/cvd/command_sequence.h"
31 #include "host/commands/cvd/instance_lock.h"
32 #include "host/commands/cvd/server_client.h"
33
34 namespace cuttlefish {
35
36 namespace {
37
38 struct ConvertedAcloudCreateCommand {
39 InstanceLockFile lock;
40 std::vector<RequestWithStdio> requests;
41 };
42
43 /**
44 * Split a string into arguments based on shell tokenization rules.
45 *
46 * This behaves like `shlex.split` from python where arguments are separated
47 * based on whitespace, but quoting and quote escaping is respected. This
48 * function effectively removes one level of quoting from its inputs while
49 * making the split.
50 */
BashTokenize(const std::string & str)51 Result<std::vector<std::string>> BashTokenize(const std::string& str) {
52 Command command("bash");
53 command.AddParameter("-c");
54 command.AddParameter("printf '%s\n' ", str);
55 std::string stdout;
56 std::string stderr;
57 auto ret = RunWithManagedStdio(std::move(command), nullptr, &stdout, &stderr);
58 CF_EXPECT(ret == 0, "printf fail \"" << stdout << "\", \"" << stderr << "\"");
59 return android::base::Split(stdout, "\n");
60 }
61
62 class ConvertAcloudCreateCommand {
63 public:
INJECT(ConvertAcloudCreateCommand (InstanceLockFileManager & lock_file_manager))64 INJECT(ConvertAcloudCreateCommand(InstanceLockFileManager& lock_file_manager))
65 : lock_file_manager_(lock_file_manager) {}
66
Convert(const RequestWithStdio & request)67 Result<ConvertedAcloudCreateCommand> Convert(
68 const RequestWithStdio& request) {
69 auto arguments = ParseInvocation(request.Message()).arguments;
70 CF_EXPECT(arguments.size() > 0);
71 CF_EXPECT(arguments[0] == "create");
72 arguments.erase(arguments.begin());
73
74 const auto& request_command = request.Message().command_request();
75
76 std::vector<Flag> flags;
77 bool local_instance_set;
78 std::optional<int> local_instance;
79 auto local_instance_flag = Flag();
80 local_instance_flag.Alias(
81 {FlagAliasMode::kFlagConsumesArbitrary, "--local-instance"});
82 local_instance_flag.Setter([&local_instance_set,
83 &local_instance](const FlagMatch& m) {
84 local_instance_set = true;
85 if (m.value != "" && local_instance) {
86 LOG(ERROR) << "Instance number already set, was \"" << *local_instance
87 << "\", now set to \"" << m.value << "\"";
88 return false;
89 } else if (m.value != "" && !local_instance) {
90 local_instance = std::stoi(m.value);
91 }
92 return true;
93 });
94 flags.emplace_back(local_instance_flag);
95
96 bool verbose = false;
97 flags.emplace_back(Flag()
98 .Alias({FlagAliasMode::kFlagExact, "-v"})
99 .Alias({FlagAliasMode::kFlagExact, "-vv"})
100 .Alias({FlagAliasMode::kFlagExact, "--verbose"})
101 .Setter([&verbose](const FlagMatch&) {
102 verbose = true;
103 return true;
104 }));
105
106 std::optional<std::string> branch;
107 flags.emplace_back(
108 Flag()
109 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--branch"})
110 .Setter([&branch](const FlagMatch& m) {
111 branch = m.value;
112 return true;
113 }));
114
115 bool local_image;
116 flags.emplace_back(
117 Flag()
118 .Alias({FlagAliasMode::kFlagConsumesArbitrary, "--local-image"})
119 .Setter([&local_image](const FlagMatch& m) {
120 local_image = true;
121 return m.value == "";
122 }));
123
124 std::optional<std::string> build_id;
125 flags.emplace_back(
126 Flag()
127 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-id"})
128 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_id"})
129 .Setter([&build_id](const FlagMatch& m) {
130 build_id = m.value;
131 return true;
132 }));
133
134 std::optional<std::string> build_target;
135 flags.emplace_back(
136 Flag()
137 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build-target"})
138 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--build_target"})
139 .Setter([&build_target](const FlagMatch& m) {
140 build_target = m.value;
141 return true;
142 }));
143
144 std::optional<std::string> launch_args;
145 flags.emplace_back(
146 Flag()
147 .Alias({FlagAliasMode::kFlagConsumesFollowing, "--launch-args"})
148 .Setter([&launch_args](const FlagMatch& m) {
149 launch_args = m.value;
150 return true;
151 }));
152
153 CF_EXPECT(ParseFlags(flags, arguments));
154 CF_EXPECT(arguments.size() == 0,
155 "Unrecognized arguments:'"
156 << android::base::Join(arguments, "', '") << "'");
157
158 CF_EXPECT(local_instance_set == true,
159 "Only '--local-instance' is supported");
160 std::optional<InstanceLockFile> lock;
161 if (local_instance.has_value()) {
162 // TODO(schuffelen): Block here if it can be interruptible
163 lock = CF_EXPECT(lock_file_manager_.TryAcquireLock(*local_instance));
164 } else {
165 lock = CF_EXPECT(lock_file_manager_.TryAcquireUnusedLock());
166 }
167 CF_EXPECT(lock.has_value(), "Could not acquire instance lock");
168 CF_EXPECT(CF_EXPECT(lock->Status()) == InUseState::kNotInUse);
169
170 auto dir = TempDir() + "/acloud_cvd_temp/local-instance-" +
171 std::to_string(lock->Instance());
172
173 static constexpr char kAndroidHostOut[] = "ANDROID_HOST_OUT";
174
175 auto host_artifacts_path = request_command.env().find(kAndroidHostOut);
176 CF_EXPECT(host_artifacts_path != request_command.env().end(),
177 "Missing " << kAndroidHostOut);
178
179 std::vector<cvd::Request> request_protos;
180 if (local_image) {
181 cvd::Request& mkdir_request = request_protos.emplace_back();
182 auto& mkdir_command = *mkdir_request.mutable_command_request();
183 mkdir_command.add_args("cvd");
184 mkdir_command.add_args("mkdir");
185 mkdir_command.add_args("-p");
186 mkdir_command.add_args(dir);
187 auto& mkdir_env = *mkdir_command.mutable_env();
188 mkdir_env[kAndroidHostOut] = host_artifacts_path->second;
189 *mkdir_command.mutable_working_directory() = dir;
190 } else {
191 cvd::Request& fetch_request = request_protos.emplace_back();
192 auto& fetch_command = *fetch_request.mutable_command_request();
193 fetch_command.add_args("cvd");
194 fetch_command.add_args("fetch");
195 fetch_command.add_args("--directory");
196 fetch_command.add_args(dir);
197 if (branch || build_id || build_target) {
198 fetch_command.add_args("--default_build");
199 auto target = build_target ? "/" + *build_target : "";
200 auto build = build_id.value_or(branch.value_or("aosp-master"));
201 fetch_command.add_args(build + target);
202 }
203 *fetch_command.mutable_working_directory() = dir;
204 auto& fetch_env = *fetch_command.mutable_env();
205 fetch_env[kAndroidHostOut] = host_artifacts_path->second;
206 }
207
208 cvd::Request& start_request = request_protos.emplace_back();
209 auto& start_command = *start_request.mutable_command_request();
210 start_command.add_args("cvd");
211 start_command.add_args("start");
212 start_command.add_args("--daemon");
213 start_command.add_args("--undefok");
214 start_command.add_args("report_anonymous_usage_stats");
215 start_command.add_args("--report_anonymous_usage_stats");
216 start_command.add_args("y");
217 if (launch_args) {
218 for (const auto& arg : CF_EXPECT(BashTokenize(*launch_args))) {
219 start_command.add_args(arg);
220 }
221 }
222 static constexpr char kAndroidProductOut[] = "ANDROID_PRODUCT_OUT";
223 auto& start_env = *start_command.mutable_env();
224 if (local_image) {
225 start_env[kAndroidHostOut] = host_artifacts_path->second;
226
227 auto product_out = request_command.env().find(kAndroidProductOut);
228 CF_EXPECT(product_out != request_command.env().end(),
229 "Missing " << kAndroidProductOut);
230 start_env[kAndroidProductOut] = product_out->second;
231 } else {
232 start_env[kAndroidHostOut] = dir;
233 start_env[kAndroidProductOut] = dir;
234 }
235 start_env["CUTTLEFISH_INSTANCE"] = std::to_string(lock->Instance());
236 start_env["HOME"] = dir;
237 *start_command.mutable_working_directory() = dir;
238
239 std::vector<SharedFD> fds;
240 if (verbose) {
241 fds = request.FileDescriptors();
242 } else {
243 auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
244 CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
245 fds = {dev_null, dev_null, dev_null};
246 }
247
248 ConvertedAcloudCreateCommand ret = {
249 .lock = {std::move(*lock)},
250 };
251 for (auto& request_proto : request_protos) {
252 ret.requests.emplace_back(request_proto, fds, request.Credentials());
253 }
254 return ret;
255 }
256
257 private:
258 InstanceLockFileManager& lock_file_manager_;
259 };
260
261 class TryAcloudCreateCommand : public CvdServerHandler {
262 public:
INJECT(TryAcloudCreateCommand (ConvertAcloudCreateCommand & converter))263 INJECT(TryAcloudCreateCommand(ConvertAcloudCreateCommand& converter))
264 : converter_(converter) {}
265 ~TryAcloudCreateCommand() = default;
266
CanHandle(const RequestWithStdio & request) const267 Result<bool> CanHandle(const RequestWithStdio& request) const override {
268 auto invocation = ParseInvocation(request.Message());
269 return invocation.command == "try-acloud" &&
270 invocation.arguments.size() >= 1 &&
271 invocation.arguments[0] == "create";
272 }
Handle(const RequestWithStdio & request)273 Result<cvd::Response> Handle(const RequestWithStdio& request) override {
274 CF_EXPECT(converter_.Convert(request));
275 return CF_ERR("Unreleased");
276 }
Interrupt()277 Result<void> Interrupt() override { return CF_ERR("Can't be interrupted."); }
278
279 private:
280 ConvertAcloudCreateCommand& converter_;
281 };
282
283 class AcloudCreateCommand : public CvdServerHandler {
284 public:
INJECT(AcloudCreateCommand (CommandSequenceExecutor & executor,ConvertAcloudCreateCommand & converter))285 INJECT(AcloudCreateCommand(CommandSequenceExecutor& executor,
286 ConvertAcloudCreateCommand& converter))
287 : executor_(executor), converter_(converter) {}
288 ~AcloudCreateCommand() = default;
289
CanHandle(const RequestWithStdio & request) const290 Result<bool> CanHandle(const RequestWithStdio& request) const override {
291 auto invocation = ParseInvocation(request.Message());
292 return invocation.command == "acloud" && invocation.arguments.size() >= 1 &&
293 invocation.arguments[0] == "create";
294 }
Handle(const RequestWithStdio & request)295 Result<cvd::Response> Handle(const RequestWithStdio& request) override {
296 std::unique_lock interrupt_lock(interrupt_mutex_);
297 if (interrupted_) {
298 return CF_ERR("Interrupted");
299 }
300 CF_EXPECT(CanHandle(request));
301
302 auto converted = CF_EXPECT(converter_.Convert(request));
303 interrupt_lock.unlock();
304 CF_EXPECT(executor_.Execute(converted.requests, request.Err()));
305
306 CF_EXPECT(converted.lock.Status(InUseState::kInUse));
307
308 cvd::Response response;
309 response.mutable_command_response();
310 return response;
311 }
Interrupt()312 Result<void> Interrupt() override {
313 std::scoped_lock interrupt_lock(interrupt_mutex_);
314 interrupted_ = true;
315 CF_EXPECT(executor_.Interrupt());
316 return {};
317 }
318
319 private:
320 CommandSequenceExecutor& executor_;
321 ConvertAcloudCreateCommand& converter_;
322
323 std::mutex interrupt_mutex_;
324 bool interrupted_ = false;
325 };
326
327 } // namespace
328
AcloudCommandComponent()329 fruit::Component<fruit::Required<CvdCommandHandler>> AcloudCommandComponent() {
330 return fruit::createComponent()
331 .addMultibinding<CvdServerHandler, AcloudCreateCommand>()
332 .addMultibinding<CvdServerHandler, TryAcloudCreateCommand>();
333 }
334
335 } // namespace cuttlefish
336