1 /*
2 * Copyright (C) 2023 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/run_cvd/server_loop_impl.h"
18
19 #include <sstream>
20 #include <string>
21
22 #include <android-base/file.h>
23
24 #include "common/libs/fs/shared_buf.h"
25 #include "common/libs/fs/shared_fd.h"
26 #include "common/libs/utils/contains.h"
27 #include "common/libs/utils/files.h"
28 #include "common/libs/utils/json.h"
29 #include "common/libs/utils/result.h"
30 #include "host/libs/command_util/runner/defs.h"
31 #include "host/libs/command_util/snapshot_utils.h"
32 #include "host/libs/command_util/util.h"
33 #include "host/libs/vm_manager/crosvm_manager.h"
34 #include "host/libs/vm_manager/qemu_manager.h"
35 #include "run_cvd.pb.h"
36
37 namespace cuttlefish {
38 namespace run_cvd_impl {
39 using APBootFlow = CuttlefishConfig::InstanceSpecific::APBootFlow;
40
41 std::unordered_map<std::string, std::string>
InitializeVmToControlSockPath(const CuttlefishConfig::InstanceSpecific & instance)42 ServerLoopImpl::InitializeVmToControlSockPath(
43 const CuttlefishConfig::InstanceSpecific& instance) {
44 return std::unordered_map<std::string, std::string>{
45 // TODO(kwstephenkim): add the following two lines to support QEMU
46 // {ToString(VmmMode::kQemu),
47 // instance.PerInstanceInternalUdsPath("qemu_monitor.sock")},
48 {ToString(VmmMode::kCrosvm), instance.CrosvmSocketPath()},
49 {cuttlefish::kApName, instance.OpenwrtCrosvmSocketPath()},
50 };
51 }
52
SubtoolPath(const std::string & subtool_name)53 static std::string SubtoolPath(const std::string& subtool_name) {
54 auto my_own_dir = android::base::GetExecutableDirectory();
55 std::stringstream subtool_path_stream;
56 subtool_path_stream << my_own_dir << "/" << subtool_name;
57 auto subtool_path = subtool_path_stream.str();
58 if (my_own_dir.empty() || !FileExists(subtool_path)) {
59 return HostBinaryPath(subtool_name);
60 }
61 return subtool_path;
62 }
63
GetSocketPath(const std::string name,std::unordered_map<std::string,std::string> & vm_name_to_control_sock_)64 static std::string GetSocketPath(
65 const std::string name,
66 std::unordered_map<std::string, std::string>& vm_name_to_control_sock_) {
67 if (!Contains(vm_name_to_control_sock_, name)) {
68 return "";
69 }
70 return vm_name_to_control_sock_.at(name);
71 }
72
SuspendCrosvm(const std::string & vm_sock_path)73 static Result<void> SuspendCrosvm(const std::string& vm_sock_path) {
74 const auto crosvm_bin_path = SubtoolPath("crosvm");
75 std::vector<std::string> command_args{crosvm_bin_path, "suspend",
76 vm_sock_path, "--full"};
77 auto infop = CF_EXPECT(Execute(command_args, SubprocessOptions(), WEXITED));
78 CF_EXPECT_EQ(infop.si_code, CLD_EXITED);
79 CF_EXPECTF(infop.si_status == 0, "crosvm suspend returns non zero code {}",
80 infop.si_status);
81 return {};
82 }
83
ResumeCrosvm(const std::string & vm_sock_path)84 static Result<void> ResumeCrosvm(const std::string& vm_sock_path) {
85 const auto crosvm_bin_path = SubtoolPath("crosvm");
86 std::vector<std::string> command_args{crosvm_bin_path, "resume", vm_sock_path,
87 "--full"};
88 auto infop = CF_EXPECT(Execute(command_args, SubprocessOptions(), WEXITED));
89 CF_EXPECT_EQ(infop.si_code, CLD_EXITED);
90 CF_EXPECTF(infop.si_status == 0, "crosvm resume returns non zero code {}",
91 infop.si_status);
92 return {};
93 }
94
SuspendGuest()95 Result<void> ServerLoopImpl::SuspendGuest() {
96 // If openwrt is running in crosvm, suspend it.
97 const auto ap_vm_name = config_.ap_vm_manager();
98 if (instance_.ap_boot_flow() != APBootFlow::None &&
99 ap_vm_name == cuttlefish::kApName) {
100 const auto openwrt_sock =
101 GetSocketPath(ap_vm_name, vm_name_to_control_sock_);
102 if (openwrt_sock == "") {
103 return CF_ERR("The vm_manager " + ap_vm_name + " is not supported yet");
104 }
105 CF_EXPECT(SuspendCrosvm(openwrt_sock),
106 "failed to suspend openwrt crosvm instance.");
107 }
108 const auto main_vmm = config_.vm_manager();
109 if (main_vmm == VmmMode::kCrosvm) {
110 const auto& vm_sock =
111 GetSocketPath(ToString(main_vmm), vm_name_to_control_sock_);
112 if (vm_sock == "") {
113 return CF_ERR("The vm_manager " << main_vmm << " is not supported yet");
114 }
115 return SuspendCrosvm(vm_sock);
116 } else {
117 return CF_ERR("The vm_manager " << main_vmm << " is not supported yet");
118 }
119 }
120
ResumeGuest()121 Result<void> ServerLoopImpl::ResumeGuest() {
122 // If openwrt is running in crosvm, resume it.
123 const auto ap_vm_name = config_.ap_vm_manager();
124 if (instance_.ap_boot_flow() != APBootFlow::None &&
125 ap_vm_name == cuttlefish::kApName) {
126 const auto& openwrt_sock =
127 GetSocketPath(ap_vm_name, vm_name_to_control_sock_);
128 if (openwrt_sock == "") {
129 return CF_ERR("The vm_manager " + ap_vm_name + " is not supported yet");
130 }
131 CF_EXPECT(ResumeCrosvm(openwrt_sock),
132 "failed to resume openwrt crosvm instance.");
133 }
134 const auto main_vmm = config_.vm_manager();
135 if (main_vmm == VmmMode::kCrosvm) {
136 const auto& vm_sock =
137 GetSocketPath(ToString(main_vmm), vm_name_to_control_sock_);
138 if (vm_sock == "") {
139 return CF_ERR("The vm_manager " << main_vmm << " is not supported yet");
140 }
141 return ResumeCrosvm(vm_sock);
142 } else {
143 return CF_ERR("The vm_manager " << main_vmm << " is not supported yet");
144 }
145 }
146
RunAdbShellCommand(const CuttlefishConfig::InstanceSpecific & ins,const std::vector<std::string> & command_args)147 static Result<void> RunAdbShellCommand(
148 const CuttlefishConfig::InstanceSpecific& ins,
149 const std::vector<std::string>& command_args) {
150 // Make sure device is connected, otherwise the following `adb -s SERIAL
151 // wait-for-device shell ...` would get stuck.
152 Command connect_cmd(SubtoolPath("adb"));
153 // Avoid the adb server being started in the runtime directory and looking
154 // like a process that is still using the directory.
155 connect_cmd.SetWorkingDirectory("/");
156 connect_cmd.AddParameter("connect");
157 connect_cmd.AddParameter(ins.adb_ip_and_port());
158 CF_EXPECT_EQ(connect_cmd.Start().Wait(), 0);
159
160 // Run the shell commands.
161 Command shell_cmd(SubtoolPath("adb"));
162 shell_cmd.AddParameter("-s").AddParameter(ins.adb_ip_and_port());
163 shell_cmd.AddParameter("shell");
164 for (const auto& argument : command_args) {
165 shell_cmd.AddParameter(argument);
166 }
167 CF_EXPECT_EQ(shell_cmd.Start().Wait(), 0);
168 return {};
169 }
170
HandleSuspend(ProcessMonitor & process_monitor)171 Result<void> ServerLoopImpl::HandleSuspend(ProcessMonitor& process_monitor) {
172 // right order: guest -> host
173 LOG(DEBUG) << "Suspending the guest..";
174 CF_EXPECT(
175 RunAdbShellCommand(instance_, {"/vendor/bin/snapshot_hook_pre_suspend"}));
176 CF_EXPECT(SuspendGuest());
177 LOG(DEBUG) << "The guest is suspended.";
178 CF_EXPECT(process_monitor.SuspendMonitoredProcesses(),
179 "Failed to suspend host processes.");
180 LOG(DEBUG) << "The host processes are suspended.";
181 return {};
182 }
183
HandleResume(ProcessMonitor & process_monitor)184 Result<void> ServerLoopImpl::HandleResume(ProcessMonitor& process_monitor) {
185 // right order: host -> guest
186 CF_EXPECT(process_monitor.ResumeMonitoredProcesses(),
187 "Failed to resume host processes.");
188 LOG(DEBUG) << "The host processes are resumed.";
189 LOG(DEBUG) << "Resuming the guest..";
190 CF_EXPECT(ResumeGuest());
191 CF_EXPECT(
192 RunAdbShellCommand(instance_, {"/vendor/bin/snapshot_hook_post_resume"}));
193 LOG(DEBUG) << "The guest resumed.";
194 return {};
195 }
196
TakeCrosvmGuestSnapshot(const Json::Value & meta_json)197 Result<void> ServerLoopImpl::TakeCrosvmGuestSnapshot(
198 const Json::Value& meta_json) {
199 const auto snapshots_parent_dir =
200 CF_EXPECT(InstanceGuestSnapshotPath(meta_json, instance_.id()));
201 const auto crosvm_bin = config_.crosvm_binary();
202 const std::string snapshot_guest_param =
203 snapshots_parent_dir + "/" + kGuestSnapshotBase;
204 // If openwrt is running in crosvm, snapshot it.
205 const auto ap_vm_name = config_.ap_vm_manager();
206 if (instance_.ap_boot_flow() != APBootFlow::None &&
207 ap_vm_name == cuttlefish::kApName) {
208 const auto& openwrt_sock =
209 GetSocketPath(ap_vm_name, vm_name_to_control_sock_);
210 if (openwrt_sock == "") {
211 return CF_ERR("The vm_manager " + ap_vm_name + " is not supported yet");
212 }
213 std::vector<std::string> openwrt_crosvm_command_args{
214 crosvm_bin, "snapshot", "take", snapshot_guest_param + "_openwrt",
215 openwrt_sock};
216 LOG(DEBUG) << "Running the following command to take snapshot..."
217 << std::endl
218 << " ";
219 for (const auto& arg : openwrt_crosvm_command_args) {
220 LOG(DEBUG) << arg << " ";
221 }
222 CF_EXPECT(Execute(openwrt_crosvm_command_args) == 0,
223 "Executing openwrt crosvm command returned -1");
224 LOG(DEBUG) << "Guest snapshot for openwrt instance #" << instance_.id()
225 << " should have been stored in " << snapshots_parent_dir
226 << "_openwrt";
227 }
228 const auto control_socket_path =
229 CF_EXPECT(VmControlSocket(), "Failed to find crosvm control.sock path.");
230 std::vector<std::string> crosvm_command_args{crosvm_bin, "snapshot", "take",
231 snapshot_guest_param,
232 control_socket_path};
233 LOG(DEBUG) << "Running the following command to take snapshot..." << std::endl
234 << " ";
235 for (const auto& arg : crosvm_command_args) {
236 LOG(DEBUG) << arg << " ";
237 }
238 CF_EXPECT(Execute(crosvm_command_args) == 0,
239 "Executing crosvm command failed");
240 LOG(DEBUG) << "Guest snapshot for instance #" << instance_.id()
241 << " should have been stored in " << snapshots_parent_dir;
242 return {};
243 }
244
245 /*
246 * Parse json file at json_path, and take guest snapshot
247 */
TakeGuestSnapshot(VmmMode vm_manager,const std::string & json_path)248 Result<void> ServerLoopImpl::TakeGuestSnapshot(VmmMode vm_manager,
249 const std::string& json_path) {
250 // common code across vm_manager
251 CF_EXPECTF(FileExists(json_path), "{} must exist but does not.", json_path);
252 SharedFD json_fd = SharedFD::Open(json_path, O_RDONLY);
253 CF_EXPECTF(json_fd->IsOpen(), "Failed to open {}", json_path);
254 std::string json_contents;
255 CF_EXPECT_GE(ReadAll(json_fd, &json_contents), 0,
256 std::string("Failed to read from ") + json_path);
257 Json::Value meta_json = CF_EXPECTF(
258 ParseJson(json_contents), "Failed to parse json: \n{}", json_contents);
259 CF_EXPECTF(vm_manager == VmmMode::kCrosvm,
260 "{}, which is not crosvm, is not yet supported.", vm_manager);
261 CF_EXPECT(TakeCrosvmGuestSnapshot(meta_json),
262 "TakeCrosvmGuestSnapshot() failed.");
263 return {};
264 }
265
HandleSnapshotTake(const run_cvd::SnapshotTake & snapshot_take)266 Result<void> ServerLoopImpl::HandleSnapshotTake(
267 const run_cvd::SnapshotTake& snapshot_take) {
268 CF_EXPECT(!snapshot_take.snapshot_path().empty(),
269 "snapshot_path must be non-empty");
270 CF_EXPECT(
271 TakeGuestSnapshot(config_.vm_manager(), snapshot_take.snapshot_path()),
272 "Failed to take guest snapshot");
273 return {};
274 }
275
276 } // namespace run_cvd_impl
277 } // namespace cuttlefish
278