• 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 #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