1 /*
2  * Copyright (C) 2019 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/libs/vm_manager/crosvm_manager.h"
18 
19 #include <sys/stat.h>
20 #include <sys/types.h>
21 
22 #include <cassert>
23 #include <string>
24 #include <vector>
25 
26 #include <android-base/strings.h>
27 #include <android-base/logging.h>
28 
29 #include "common/libs/utils/environment.h"
30 #include "common/libs/utils/network.h"
31 #include "common/libs/utils/subprocess.h"
32 #include "common/libs/utils/files.h"
33 #include "host/libs/config/cuttlefish_config.h"
34 #include "host/libs/vm_manager/qemu_manager.h"
35 
36 namespace cuttlefish {
37 namespace vm_manager {
38 
39 namespace {
40 
GetControlSocketPath(const cuttlefish::CuttlefishConfig * config)41 std::string GetControlSocketPath(const cuttlefish::CuttlefishConfig* config) {
42   return config->ForDefaultInstance()
43       .PerInstanceInternalPath("crosvm_control.sock");
44 }
45 
AddTapFdParameter(cuttlefish::Command * crosvm_cmd,const std::string & tap_name)46 cuttlefish::SharedFD AddTapFdParameter(cuttlefish::Command* crosvm_cmd,
47                                 const std::string& tap_name) {
48   auto tap_fd = cuttlefish::OpenTapInterface(tap_name);
49   if (tap_fd->IsOpen()) {
50     crosvm_cmd->AddParameter("--tap-fd=", tap_fd);
51   } else {
52     LOG(ERROR) << "Unable to connect to " << tap_name << ": "
53                << tap_fd->StrError();
54   }
55   return tap_fd;
56 }
57 
ReleaseDhcpLeases(const std::string & lease_path,cuttlefish::SharedFD tap_fd)58 bool ReleaseDhcpLeases(const std::string& lease_path, cuttlefish::SharedFD tap_fd) {
59   auto lease_file_fd = cuttlefish::SharedFD::Open(lease_path, O_RDONLY);
60   if (!lease_file_fd->IsOpen()) {
61     LOG(ERROR) << "Could not open leases file \"" << lease_path << '"';
62     return false;
63   }
64   bool success = true;
65   auto dhcp_leases = cuttlefish::ParseDnsmasqLeases(lease_file_fd);
66   for (auto& lease : dhcp_leases) {
67     std::uint8_t dhcp_server_ip[] = {192, 168, 96, (std::uint8_t) (cuttlefish::ForCurrentInstance(1) * 4 - 3)};
68     if (!cuttlefish::ReleaseDhcp4(tap_fd, lease.mac_address, lease.ip_address, dhcp_server_ip)) {
69       LOG(ERROR) << "Failed to release " << lease;
70       success = false;
71     } else {
72       LOG(INFO) << "Successfully dropped " << lease;
73     }
74   }
75   return success;
76 }
77 
Stop()78 bool Stop() {
79   auto config = cuttlefish::CuttlefishConfig::Get();
80   cuttlefish::Command command(config->crosvm_binary());
81   command.AddParameter("stop");
82   command.AddParameter(GetControlSocketPath(config));
83 
84   auto process = command.Start();
85 
86   return process.Wait() == 0;
87 }
88 
89 }  // namespace
90 
name()91 const std::string CrosvmManager::name() { return "crosvm"; }
92 
ConfigureGpu(const std::string & gpu_mode)93 std::vector<std::string> CrosvmManager::ConfigureGpu(const std::string& gpu_mode) {
94   // Override the default HAL search paths in all cases. We do this because
95   // the HAL search path allows for fallbacks, and fallbacks in conjunction
96   // with properities lead to non-deterministic behavior while loading the
97   // HALs.
98   if (gpu_mode == cuttlefish::kGpuModeGuestSwiftshader) {
99     return {
100         "androidboot.hardware.gralloc=minigbm",
101         "androidboot.hardware.hwcomposer=cutf_hwc2",
102         "androidboot.hardware.egl=swiftshader",
103         "androidboot.hardware.vulkan=pastel",
104     };
105   }
106 
107   // Try to load the Nvidia modeset kernel module. Running Crosvm with Nvidia's EGL library on a
108   // fresh machine after a boot will fail because the Nvidia EGL library will fork to run the
109   // nvidia-modprobe command and the main Crosvm process will abort after receiving the exit signal
110   // of the forked child which is interpreted as a failure.
111   cuttlefish::Command modprobe_cmd("/usr/bin/nvidia-modprobe");
112   modprobe_cmd.AddParameter("--modeset");
113   modprobe_cmd.Start().Wait();
114 
115   if (gpu_mode == cuttlefish::kGpuModeDrmVirgl) {
116     return {
117       "androidboot.hardware.gralloc=minigbm",
118       "androidboot.hardware.hwcomposer=drm_minigbm",
119       "androidboot.hardware.egl=mesa",
120     };
121   }
122   if (gpu_mode == cuttlefish::kGpuModeGfxStream) {
123     return {
124         "androidboot.hardware.gralloc=minigbm",
125         "androidboot.hardware.hwcomposer=drm_minigbm",
126         "androidboot.hardware.egl=emulation",
127         "androidboot.hardware.vulkan=ranchu",
128         "androidboot.hardware.gltransport=virtio-gpu-asg",
129     };
130   }
131   return {};
132 }
133 
ConfigureBootDevices()134 std::vector<std::string> CrosvmManager::ConfigureBootDevices() {
135   // TODO There is no way to control this assignment with crosvm (yet)
136   if (cuttlefish::HostArch() == "x86_64") {
137     // PCI domain 0, bus 0, device 4, function 0
138     return { "androidboot.boot_devices=pci0000:00/0000:00:04.0" };
139   } else {
140     return { "androidboot.boot_devices=10000.pci" };
141   }
142 }
143 
CrosvmManager(const cuttlefish::CuttlefishConfig * config)144 CrosvmManager::CrosvmManager(const cuttlefish::CuttlefishConfig* config)
145     : VmManager(config) {}
146 
StartCommands()147 std::vector<cuttlefish::Command> CrosvmManager::StartCommands() {
148   auto instance = config_->ForDefaultInstance();
149   cuttlefish::Command crosvm_cmd(config_->crosvm_binary(), [](cuttlefish::Subprocess* proc) {
150     auto stopped = Stop();
151     if (stopped) {
152       return true;
153     }
154     LOG(WARNING) << "Failed to stop VMM nicely, attempting to KILL";
155     return KillSubprocess(proc);
156   });
157   crosvm_cmd.AddParameter("run");
158 
159   auto gpu_mode = config_->gpu_mode();
160 
161   if (gpu_mode == cuttlefish::kGpuModeGuestSwiftshader) {
162     crosvm_cmd.AddParameter("--gpu=2D,",
163                             "width=", config_->x_res(), ",",
164                             "height=", config_->y_res());
165   } else if (gpu_mode == cuttlefish::kGpuModeDrmVirgl ||
166              gpu_mode == cuttlefish::kGpuModeGfxStream) {
167     crosvm_cmd.AddParameter(gpu_mode == cuttlefish::kGpuModeGfxStream ?
168                                 "--gpu=gfxstream," : "--gpu=",
169                             "width=", config_->x_res(), ",",
170                             "height=", config_->y_res(), ",",
171                             "egl=true,surfaceless=true,glx=false,gles=true");
172     crosvm_cmd.AddParameter("--wayland-sock=", instance.frames_socket_path());
173   }
174   if (!config_->final_ramdisk_path().empty()) {
175     crosvm_cmd.AddParameter("--initrd=", config_->final_ramdisk_path());
176   }
177   // crosvm_cmd.AddParameter("--null-audio");
178   crosvm_cmd.AddParameter("--mem=", config_->memory_mb());
179   crosvm_cmd.AddParameter("--cpus=", config_->cpus());
180   crosvm_cmd.AddParameter("--params=", kernel_cmdline_);
181   for (const auto& disk : instance.virtual_disk_paths()) {
182     crosvm_cmd.AddParameter("--rwdisk=", disk);
183   }
184   crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config_));
185 
186   if (frontend_enabled_) {
187     crosvm_cmd.AddParameter("--single-touch=", instance.touch_socket_path(),
188                             ":", config_->x_res(), ":", config_->y_res());
189     crosvm_cmd.AddParameter("--keyboard=", instance.keyboard_socket_path());
190   }
191 
192   auto wifi_tap = AddTapFdParameter(&crosvm_cmd, instance.wifi_tap_name());
193   AddTapFdParameter(&crosvm_cmd, instance.mobile_tap_name());
194 
195   crosvm_cmd.AddParameter("--rw-pmem-device=", instance.access_kregistry_path());
196   crosvm_cmd.AddParameter("--pstore=path=", instance.pstore_path(), ",size=",
197                           cuttlefish::FileSize(instance.pstore_path()));
198 
199   if (config_->enable_sandbox()) {
200     const bool seccomp_exists = cuttlefish::DirectoryExists(config_->seccomp_policy_dir());
201     const std::string& var_empty_dir = cuttlefish::kCrosvmVarEmptyDir;
202     const bool var_empty_available = cuttlefish::DirectoryExists(var_empty_dir);
203     if (!var_empty_available || !seccomp_exists) {
204       LOG(FATAL) << var_empty_dir << " is not an existing, empty directory."
205                  << "seccomp-policy-dir, " << config_->seccomp_policy_dir()
206                  << " does not exist " << std::endl;
207       return {};
208     }
209     crosvm_cmd.AddParameter("--seccomp-policy-dir=", config_->seccomp_policy_dir());
210   } else {
211     crosvm_cmd.AddParameter("--disable-sandbox");
212   }
213 
214   if (instance.vsock_guest_cid() >= 2) {
215     crosvm_cmd.AddParameter("--cid=", instance.vsock_guest_cid());
216   }
217 
218   // Use an 8250 UART (ISA or platform device) for earlycon, as the
219   // virtio-console driver may not be available for early messages
220   // In kgdb mode, earlycon is an interactive console, and so early
221   // dmesg will go there instead of the kernel.log
222   if (!(config_->use_bootloader() || config_->kgdb())) {
223     crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
224                             instance.kernel_log_pipe_name(), ",earlycon=true");
225   }
226 
227   // Use a virtio-console instance for the main kernel console. All
228   // messages will switch from earlycon to virtio-console after the driver
229   // is loaded, and crosvm will append to the kernel log automatically
230   crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=1,type=file,path=",
231                           instance.kernel_log_pipe_name(), ",console=true");
232 
233   auto console_in_pipe_name = instance.console_in_pipe_name();
234   if (mkfifo(console_in_pipe_name.c_str(), 0600) != 0) {
235     auto error = errno;
236     LOG(ERROR) << "Failed to create console input fifo for crosvm: "
237                << strerror(error);
238     return {};
239   }
240   auto console_out_pipe_name = instance.console_out_pipe_name();
241   if (mkfifo(console_out_pipe_name.c_str(), 0660) != 0) {
242     auto error = errno;
243     LOG(ERROR) << "Failed to create console output fifo for crosvm: "
244                << strerror(error);
245     return {};
246   }
247 
248   // These fds will only be read from or written to, but open them with
249   // read and write access to keep them open in case the subprocesses exit
250   cuttlefish::SharedFD console_in_wr =
251       cuttlefish::SharedFD::Open(console_in_pipe_name.c_str(), O_RDWR);
252   if (!console_in_wr->IsOpen()) {
253     LOG(ERROR) << "Failed to open console input fifo for writes: "
254                << console_in_wr->StrError();
255     return {};
256   }
257   cuttlefish::SharedFD console_out_rd =
258       cuttlefish::SharedFD::Open(console_out_pipe_name.c_str(), O_RDWR);
259   if (!console_out_rd->IsOpen()) {
260     LOG(ERROR) << "Failed to open console output fifo for reads: "
261                << console_out_rd->StrError();
262     return {};
263   }
264 
265   // stdin is the only currently supported way to write data to a serial port in
266   // crosvm. A file (named pipe) is used here instead of stdout to ensure only
267   // the serial port output is received by the console forwarder as crosvm may
268   // print other messages to stdout.
269   if (config_->kgdb() || config_->use_bootloader()) {
270     crosvm_cmd.AddParameter("--serial=hardware=serial,num=1,type=file,path=",
271                             console_out_pipe_name, ",input=", console_in_pipe_name,
272                             ",earlycon=true");
273     // In kgdb mode, we have the interactive console on ttyS0 (both Android's
274     // console and kdb), so we can disable the virtio-console port usually
275     // allocated to Android's serial console, and redirect it to a sink. This
276     // ensures that that the PCI device assignments (and thus sepolicy) don't
277     // have to change
278     crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=sink");
279   } else {
280     crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=2,type=file,path=",
281                             console_out_pipe_name, ",input=", console_in_pipe_name);
282   }
283 
284   cuttlefish::Command console_cmd(config_->console_forwarder_binary());
285   console_cmd.AddParameter("--console_in_fd=", console_in_wr);
286   console_cmd.AddParameter("--console_out_fd=", console_out_rd);
287 
288   cuttlefish::SharedFD log_out_rd, log_out_wr;
289   if (!cuttlefish::SharedFD::Pipe(&log_out_rd, &log_out_wr)) {
290     LOG(ERROR) << "Failed to create log pipe for crosvm's stdout/stderr: "
291                << log_out_rd->StrError();
292     return {};
293   }
294   crosvm_cmd.RedirectStdIO(cuttlefish::Subprocess::StdIOChannel::kStdOut,
295                            log_out_wr);
296   crosvm_cmd.RedirectStdIO(cuttlefish::Subprocess::StdIOChannel::kStdErr,
297                            log_out_wr);
298 
299   cuttlefish::Command log_tee_cmd(cuttlefish::DefaultHostArtifactsPath("bin/log_tee"));
300   log_tee_cmd.AddParameter("--process_name=crosvm");
301   log_tee_cmd.AddParameter("--log_fd_in=", log_out_rd);
302 
303   // Serial port for logcat, redirected to a pipe
304   crosvm_cmd.AddParameter("--serial=hardware=virtio-console,num=3,type=file,path=",
305                           instance.logcat_pipe_name());
306 
307   // TODO(b/162071003): virtiofs crashes without sandboxing, this should be fixed
308   if (config_->enable_sandbox()) {
309     // Set up directory shared with virtiofs
310     crosvm_cmd.AddParameter("--shared-dir=", instance.PerInstancePath(cuttlefish::kSharedDirName),
311                             ":shared:type=fs");
312   }
313 
314   // This needs to be the last parameter
315   if (config_->use_bootloader()) {
316     crosvm_cmd.AddParameter("--bios=", config_->bootloader());
317   } else {
318     crosvm_cmd.AddParameter(config_->GetKernelImageToUse());
319   }
320 
321   // Only run the leases workaround if we are not using the new network
322   // bridge architecture - in that case, we have a wider DHCP address
323   // space and stale leases should be much less of an issue
324   if (!cuttlefish::FileExists("/var/run/cuttlefish-dnsmasq-cvd-wbr.leases")) {
325     // TODO(schuffelen): QEMU also needs this and this is not the best place for
326     // this code. Find a better place to put it.
327     auto lease_file =
328         cuttlefish::ForCurrentInstance("/var/run/cuttlefish-dnsmasq-cvd-wbr-")
329         + ".leases";
330     if (!ReleaseDhcpLeases(lease_file, wifi_tap)) {
331       LOG(ERROR) << "Failed to release wifi DHCP leases. Connecting to the wifi "
332                  << "network may not work.";
333     }
334   }
335 
336   std::vector<cuttlefish::Command> ret;
337   ret.push_back(std::move(crosvm_cmd));
338   ret.push_back(std::move(console_cmd));
339   ret.push_back(std::move(log_tee_cmd));
340   return ret;
341 }
342 
343 } // namespace vm_manager
344 } // namespace cuttlefish
345 
346