• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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