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/serial_launch.h"
18
19 #include <sys/types.h>
20
21 #include <chrono>
22 #include <mutex>
23 #include <sstream>
24 #include <string>
25 #include <vector>
26
27 #include "android-base/parseint.h"
28 #include "android-base/strings.h"
29
30 #include "common/libs/fs/shared_buf.h"
31 #include "common/libs/utils/files.h"
32 #include "common/libs/utils/flag_parser.h"
33 #include "common/libs/utils/result.h"
34 #include "cvd_server.pb.h"
35 #include "host/commands/cvd/instance_lock.h"
36 #include "host/commands/cvd/selector/selector_constants.h"
37 #include "host/commands/cvd/server_client.h"
38 #include "host/commands/cvd/server_command/utils.h"
39 #include "host/commands/cvd/types.h"
40
41 // copied from "../demo_multi_vd.cpp"
42 namespace cuttlefish {
43 namespace {
44
45 template <typename... Args>
CreateCommandRequest(const google::protobuf::Map<std::string,std::string> & envs,Args &&...args)46 cvd::Request CreateCommandRequest(
47 const google::protobuf::Map<std::string, std::string>& envs,
48 Args&&... args) {
49 cvd::Request request;
50 auto& cmd_request = *request.mutable_command_request();
51 (cmd_request.add_args(std::forward<Args>(args)), ...);
52 *cmd_request.mutable_env() = envs;
53 return request;
54 }
55
AppendRequestVectors(std::vector<cvd::Request> && dest,std::vector<cvd::Request> && src)56 std::vector<cvd::Request> AppendRequestVectors(
57 std::vector<cvd::Request>&& dest, std::vector<cvd::Request>&& src) {
58 auto merged = std::move(dest);
59 for (auto& request : src) {
60 merged.emplace_back(std::move(request));
61 }
62 return merged;
63 }
64
65 struct DemoCommandSequence {
66 std::vector<InstanceLockFile> instance_locks;
67 std::vector<RequestWithStdio> requests;
68 };
69
70 /** Returns a `Flag` object that accepts comma-separated unsigned integers. */
71 template <typename T>
DeviceSpecificUintFlag(const std::string & name,std::vector<T> & values,const RequestWithStdio & request)72 Flag DeviceSpecificUintFlag(const std::string& name, std::vector<T>& values,
73 const RequestWithStdio& request) {
74 return GflagsCompatFlag(name).Setter(
75 [&request, &values](const FlagMatch& match) {
76 auto parsed_values = android::base::Tokenize(match.value, ", ");
77 for (auto& parsed_value : parsed_values) {
78 std::uint32_t num = 0;
79 if (!android::base::ParseUint(parsed_value, &num)) {
80 constexpr char kError[] = "Failed to parse integer";
81 WriteAll(request.Out(), kError, sizeof(kError));
82 return false;
83 }
84 values.push_back(num);
85 }
86 return true;
87 });
88 }
89
90 /** Returns a `Flag` object that accepts comma-separated strings. */
DeviceSpecificStringFlag(const std::string & name,std::vector<std::string> & values)91 Flag DeviceSpecificStringFlag(const std::string& name,
92 std::vector<std::string>& values) {
93 return GflagsCompatFlag(name).Setter([&values](const FlagMatch& match) {
94 auto parsed_values = android::base::Tokenize(match.value, ", ");
95 for (auto& parsed_value : parsed_values) {
96 values.push_back(parsed_value);
97 }
98 return true;
99 });
100 }
101
ParentDir(const uid_t uid)102 std::string ParentDir(const uid_t uid) {
103 constexpr char kParentDirPrefix[] = "/tmp/cvd/";
104 std::stringstream ss;
105 ss << kParentDirPrefix << uid << "/";
106 return ss.str();
107 }
108
109 } // namespace
110
111 class SerialLaunchCommand : public CvdServerHandler {
112 public:
INJECT(SerialLaunchCommand (CommandSequenceExecutor & executor,InstanceLockFileManager & lock_file_manager))113 INJECT(SerialLaunchCommand(CommandSequenceExecutor& executor,
114 InstanceLockFileManager& lock_file_manager))
115 : executor_(executor), lock_file_manager_(lock_file_manager) {}
116 ~SerialLaunchCommand() = default;
117
CanHandle(const RequestWithStdio & request) const118 Result<bool> CanHandle(const RequestWithStdio& request) const override {
119 auto invocation = ParseInvocation(request.Message());
120 return invocation.command == "experimental" &&
121 invocation.arguments.size() >= 1 &&
122 invocation.arguments[0] == "serial_launch";
123 }
Handle(const RequestWithStdio & request)124 Result<cvd::Response> Handle(const RequestWithStdio& request) override {
125 std::unique_lock interrupt_lock(interrupt_mutex_);
126 if (interrupted_) {
127 return CF_ERR("Interrupted");
128 }
129 CF_EXPECT(CF_EXPECT(CanHandle(request)));
130
131 auto commands = CF_EXPECT(CreateCommandSequence(request));
132 interrupt_lock.unlock();
133 CF_EXPECT(executor_.Execute(commands.requests, request.Err()));
134
135 for (auto& lock : commands.instance_locks) {
136 CF_EXPECT(lock.Status(InUseState::kInUse));
137 }
138
139 cvd::Response response;
140 response.mutable_command_response();
141 return response;
142 }
143
Interrupt()144 Result<void> Interrupt() override {
145 std::scoped_lock interrupt_lock(interrupt_mutex_);
146 interrupted_ = true;
147 CF_EXPECT(executor_.Interrupt());
148 return {};
149 }
150
CmdList() const151 cvd_common::Args CmdList() const override { return {"experimental"}; }
152
CreateCommandSequence(const RequestWithStdio & request)153 Result<DemoCommandSequence> CreateCommandSequence(
154 const RequestWithStdio& request) {
155 const auto& client_env = request.Message().command_request().env();
156 const auto client_uid = CF_EXPECT(request.Credentials()).uid;
157
158 std::vector<Flag> flags;
159
160 bool help = false;
161 flags.emplace_back(GflagsCompatFlag("help", help));
162
163 std::string credentials;
164 flags.emplace_back(GflagsCompatFlag("credentials", credentials));
165
166 bool verbose = false;
167 flags.emplace_back(GflagsCompatFlag("verbose", verbose));
168
169 std::vector<std::uint32_t> x_res;
170 flags.emplace_back(DeviceSpecificUintFlag("x_res", x_res, request));
171
172 std::vector<std::uint32_t> y_res;
173 flags.emplace_back(DeviceSpecificUintFlag("y_res", y_res, request));
174
175 std::vector<std::uint32_t> dpi;
176 flags.emplace_back(DeviceSpecificUintFlag("dpi", dpi, request));
177
178 std::vector<std::uint32_t> cpus;
179 flags.emplace_back(DeviceSpecificUintFlag("cpus", cpus, request));
180
181 std::vector<std::uint32_t> memory_mb;
182 flags.emplace_back(DeviceSpecificUintFlag("memory_mb", memory_mb, request));
183
184 std::vector<std::string> setupwizard_mode;
185 flags.emplace_back(
186 DeviceSpecificStringFlag("setupwizard_mode", setupwizard_mode));
187
188 std::vector<std::string> report_anonymous_usage_stats;
189 flags.emplace_back(DeviceSpecificStringFlag("report_anonymous_usage_stats",
190 report_anonymous_usage_stats));
191
192 std::vector<std::string> webrtc_device_id;
193 flags.emplace_back(
194 DeviceSpecificStringFlag("webrtc_device_id", webrtc_device_id));
195
196 bool daemon = true;
197 flags.emplace_back(GflagsCompatFlag("daemon", daemon));
198
199 struct Device {
200 std::string build;
201 std::string home_dir;
202 InstanceLockFile ins_lock;
203 };
204
205 auto time = std::chrono::system_clock::now().time_since_epoch().count();
206 std::vector<Device> devices;
207 auto& device_flag = flags.emplace_back();
208 device_flag.Alias({FlagAliasMode::kFlagPrefix, "--device="});
209 device_flag.Alias({FlagAliasMode::kFlagConsumesFollowing, "--device"});
210 device_flag.Setter(
211 [this, time, client_uid, &devices, &request](const FlagMatch& mat) {
212 auto lock = lock_file_manager_.TryAcquireUnusedLock();
213 if (!lock.ok()) {
214 WriteAll(request.Err(), lock.error().Message());
215 return false;
216 } else if (!lock->has_value()) {
217 constexpr char kError[] = "could not acquire instance lock";
218 WriteAll(request.Err(), kError, sizeof(kError));
219 return false;
220 }
221 int num = (*lock)->Instance();
222 std::string home_dir = ParentDir(client_uid) + std::to_string(time) +
223 "_" + std::to_string(num) + "/";
224 devices.emplace_back(Device{
225 .build = mat.value,
226 .home_dir = std::move(home_dir),
227 .ins_lock = std::move(**lock),
228 });
229 return true;
230 });
231
232 auto args = ParseInvocation(request.Message()).arguments;
233 for (const auto& arg : args) {
234 std::string message = "argument: \"" + arg + "\"\n";
235 CF_EXPECT(WriteAll(request.Err(), message) == message.size());
236 }
237
238 CF_EXPECT(ParseFlags(flags, args));
239
240 if (help) {
241 static constexpr char kHelp[] =
242 "Usage: cvd experimental serial_launch [--verbose] --credentials=XYZ "
243 "--device=build/target --device=build/target";
244 CF_EXPECT(WriteAll(request.Out(), kHelp, sizeof(kHelp)) == sizeof(kHelp));
245 return {};
246 }
247
248 CF_EXPECT(devices.size() < 2 || daemon,
249 "--daemon=true required for more than 1 device");
250
251 std::vector<std::vector<std::uint32_t>*> int_device_args = {
252 &x_res, &y_res, &dpi, &cpus, &memory_mb,
253 };
254 for (const auto& int_device_arg : int_device_args) {
255 CF_EXPECT(int_device_arg->size() == 0 ||
256 int_device_arg->size() == devices.size(),
257 "If given, device-specific flags should have as many values as "
258 "there are `--device` arguments");
259 }
260 std::vector<std::vector<std::string>*> string_device_args = {
261 &setupwizard_mode,
262 &report_anonymous_usage_stats,
263 &webrtc_device_id,
264 };
265 for (const auto& string_device_arg : string_device_args) {
266 CF_EXPECT(string_device_arg->size() == 0 ||
267 string_device_arg->size() == devices.size(),
268 "If given, device-specific flags should have as many values as "
269 "there are `--device` arguments");
270 }
271
272 std::vector<cvd::Request> req_protos;
273
274 auto mkdir_ancestors_requests =
275 CF_EXPECT(CreateMkdirCommandRequestRecursively(client_env,
276 ParentDir(client_uid)));
277 req_protos = AppendRequestVectors(std::move(req_protos),
278 std::move(mkdir_ancestors_requests));
279
280 bool is_first = true;
281
282 int index = 0;
283 for (const auto& device : devices) {
284 auto& mkdir_cmd = *req_protos.emplace_back().mutable_command_request();
285 *mkdir_cmd.mutable_env() = client_env;
286 mkdir_cmd.add_args("cvd");
287 mkdir_cmd.add_args("mkdir");
288 mkdir_cmd.add_args(device.home_dir);
289
290 auto& fetch_cmd = *req_protos.emplace_back().mutable_command_request();
291 *fetch_cmd.mutable_env() = client_env;
292 fetch_cmd.set_working_directory(device.home_dir);
293 fetch_cmd.add_args("cvd");
294 fetch_cmd.add_args("fetch");
295 fetch_cmd.add_args("--directory=" + device.home_dir);
296 fetch_cmd.add_args("-default_build=" + device.build);
297 fetch_cmd.add_args("-credential_source=" + credentials);
298
299 auto& launch_cmd = *req_protos.emplace_back().mutable_command_request();
300 *launch_cmd.mutable_env() = client_env;
301 launch_cmd.set_working_directory(device.home_dir);
302 (*launch_cmd.mutable_env())["HOME"] = device.home_dir;
303 (*launch_cmd.mutable_env())["ANDROID_HOST_OUT"] = device.home_dir;
304 (*launch_cmd.mutable_env())["ANDROID_PRODUCT_OUT"] = device.home_dir;
305 launch_cmd.add_args("cvd");
306 /* TODO(kwstephenkim): remove kAcquireFileLockOpt flag when
307 * SerialLaunchCommand is re-implemented so that it does not have to
308 * acquire a file lock.
309 */
310 launch_cmd.mutable_selector_opts()->add_args(
311 std::string("--") + selector::SelectorFlags::kAcquireFileLock +
312 "=false");
313 launch_cmd.add_args("start");
314 launch_cmd.add_args(
315 "--undefok=daemon,base_instance_num,x_res,y_res,dpi,cpus,memory_mb,"
316 "setupwizard_mode,report_anonymous_usage_stats,webrtc_device_id");
317 launch_cmd.add_args("--daemon");
318 launch_cmd.add_args("--base_instance_num=" +
319 std::to_string(device.ins_lock.Instance()));
320 if (index < x_res.size()) {
321 launch_cmd.add_args("--x_res=" + std::to_string(x_res[index]));
322 }
323 if (index < y_res.size()) {
324 launch_cmd.add_args("--y_res=" + std::to_string(y_res[index]));
325 }
326 if (index < dpi.size()) {
327 launch_cmd.add_args("--dpi=" + std::to_string(dpi[index]));
328 }
329 if (index < cpus.size()) {
330 launch_cmd.add_args("--cpus=" + std::to_string(cpus[index]));
331 }
332 if (index < memory_mb.size()) {
333 launch_cmd.add_args("--memory_mb=" + std::to_string(memory_mb[index]));
334 }
335 if (index < setupwizard_mode.size()) {
336 launch_cmd.add_args("--setupwizard_mode=" + setupwizard_mode[index]);
337 }
338 if (index < report_anonymous_usage_stats.size()) {
339 launch_cmd.add_args("--report_anonymous_usage_stats=" +
340 report_anonymous_usage_stats[index]);
341 }
342 if (index < webrtc_device_id.size()) {
343 launch_cmd.add_args("--webrtc_device_id=" + webrtc_device_id[index]);
344 }
345
346 index++;
347 if (is_first) {
348 is_first = false;
349 continue;
350 }
351 const auto& first = devices[0];
352 const auto& first_instance_num =
353 std::to_string(first.ins_lock.Instance());
354 auto hwsim_path = first.home_dir + "cuttlefish_runtime." +
355 first_instance_num + "/internal/vhost_user_mac80211";
356 launch_cmd.add_args("--vhost_user_mac80211_hwsim=" + hwsim_path);
357 launch_cmd.add_args("--rootcanal_instance_num=" + first_instance_num);
358 }
359
360 std::vector<SharedFD> fds;
361 if (verbose) {
362 fds = request.FileDescriptors();
363 } else {
364 auto dev_null = SharedFD::Open("/dev/null", O_RDWR);
365 CF_EXPECT(dev_null->IsOpen(), dev_null->StrError());
366 fds = {dev_null, dev_null, dev_null};
367 }
368
369 DemoCommandSequence ret;
370 for (auto& device : devices) {
371 ret.instance_locks.emplace_back(std::move(device.ins_lock));
372 }
373 for (auto& request_proto : req_protos) {
374 ret.requests.emplace_back(request.Client(), request_proto, fds,
375 request.Credentials());
376 }
377
378 return ret;
379 }
380
381 private:
CreateMkdirCommandRequestRecursively(const google::protobuf::Map<std::string,std::string> & client_env,const std::string & path)382 Result<std::vector<cvd::Request>> CreateMkdirCommandRequestRecursively(
383 const google::protobuf::Map<std::string, std::string>& client_env,
384 const std::string& path) {
385 std::vector<cvd::Request> output;
386 CF_EXPECT(!path.empty() && path.at(0) == '/',
387 "Only absolute path is supported.");
388 if (path == "/") {
389 return output;
390 }
391 std::string path_exclude_root = path.substr(1);
392 std::vector<std::string> tokens =
393 android::base::Tokenize(path_exclude_root, "/");
394 std::string current_dir = "/";
395 for (int i = 0; i < tokens.size(); i++) {
396 current_dir.append(tokens[i]);
397 if (!DirectoryExists(current_dir)) {
398 output.emplace_back(
399 CreateCommandRequest(client_env, "cvd", "mkdir", current_dir));
400 }
401 current_dir.append("/");
402 }
403 return output;
404 }
405
406 CommandSequenceExecutor& executor_;
407 InstanceLockFileManager& lock_file_manager_;
408
409 std::mutex interrupt_mutex_;
410 bool interrupted_ = false;
411 };
412
413 fruit::Component<fruit::Required<CommandSequenceExecutor>>
cvdSerialLaunchComponent()414 cvdSerialLaunchComponent() {
415 return fruit::createComponent()
416 .addMultibinding<CvdServerHandler, SerialLaunchCommand>();
417 }
418
419 } // namespace cuttlefish
420