blob: e68deb8a7e60d5c32c133b9f8adb0c819df58c54 [file] [log] [blame]
Alan Stokes17fd36a2021-09-06 17:22:37 +01001/*
2 * Copyright (C) 2021 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//! Support for starting CompOS in a VM and connecting to the service
18
19use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT};
20use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
21 IVirtualMachine::IVirtualMachine,
22 IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
23 IVirtualizationService::IVirtualizationService,
24 VirtualMachineAppConfig::VirtualMachineAppConfig,
25 VirtualMachineConfig::VirtualMachineConfig,
26};
27use android_system_virtualizationservice::binder::{
28 wait_for_interface, BinderFeatures, DeathRecipient, IBinder, Interface, ParcelFileDescriptor,
29 Result as BinderResult, Strong,
30};
31use anyhow::{anyhow, bail, Context, Result};
32use binder::{
33 unstable_api::{new_spibinder, AIBinder},
34 FromIBinder,
35};
36use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
Alan Stokesf03d81a2021-09-20 17:44:03 +010037use log::{info, warn};
Alan Stokes17fd36a2021-09-06 17:22:37 +010038use std::fs::File;
Alan Stokesf03d81a2021-09-20 17:44:03 +010039use std::io::{BufRead, BufReader};
Alan Stokes714fcc22021-09-14 17:43:09 +010040use std::os::raw;
41use std::os::unix::io::IntoRawFd;
Alan Stokes17fd36a2021-09-06 17:22:37 +010042use std::path::Path;
43use std::sync::{Arc, Condvar, Mutex};
44use std::thread;
45use std::time::Duration;
46
47/// This owns an instance of the CompOS VM.
48pub struct VmInstance {
Alan Stokesf03d81a2021-09-20 17:44:03 +010049 #[allow(dead_code)] // Prevent service manager from killing the dynamic service
50 service: Strong<dyn IVirtualizationService>,
51 #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
Alan Stokes17fd36a2021-09-06 17:22:37 +010052 vm: Strong<dyn IVirtualMachine>,
Alan Stokesa2869d22021-09-22 09:06:41 +010053 #[allow(dead_code)] // TODO: Do we need this?
Alan Stokes17fd36a2021-09-06 17:22:37 +010054 cid: i32,
55}
56
57impl VmInstance {
Alan Stokes69c610f2021-09-27 14:03:31 +010058 /// Return a new connection to the Virtualization Service binder interface. This will start the
59 /// service if necessary.
60 pub fn connect_to_virtualization_service() -> Result<Strong<dyn IVirtualizationService>> {
61 wait_for_interface::<dyn IVirtualizationService>("android.system.virtualizationservice")
62 .context("Failed to find VirtualizationService")
63 }
64
Alan Stokes17fd36a2021-09-06 17:22:37 +010065 /// Start a new CompOS VM instance using the specified instance image file.
66 pub fn start(instance_image: &Path) -> Result<VmInstance> {
67 let instance_image =
68 File::open(instance_image).context("Failed to open instance image file")?;
69 let instance_fd = ParcelFileDescriptor::new(instance_image);
70
71 let apex_dir = Path::new(COMPOS_APEX_ROOT);
72 let data_dir = Path::new(COMPOS_DATA_ROOT);
73
74 let apk_fd = File::open(apex_dir.join("app/CompOSPayloadApp/CompOSPayloadApp.apk"))
75 .context("Failed to open config APK file")?;
76 let apk_fd = ParcelFileDescriptor::new(apk_fd);
77
78 let idsig_fd = File::open(apex_dir.join("etc/CompOSPayloadApp.apk.idsig"))
79 .context("Failed to open config APK idsig file")?;
80 let idsig_fd = ParcelFileDescriptor::new(idsig_fd);
81
82 // TODO: Send this to stdout instead? Or specify None?
83 let log_fd = File::create(data_dir.join("vm.log")).context("Failed to create log file")?;
84 let log_fd = ParcelFileDescriptor::new(log_fd);
85
86 let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
87 apk: Some(apk_fd),
88 idsig: Some(idsig_fd),
89 instanceImage: Some(instance_fd),
90 configPath: "assets/vm_config.json".to_owned(),
91 ..Default::default()
92 });
93
Alan Stokes69c610f2021-09-27 14:03:31 +010094 let service = Self::connect_to_virtualization_service()?;
Alan Stokes17fd36a2021-09-06 17:22:37 +010095
Andrew Walbranf8d94112021-09-07 11:45:36 +000096 let vm = service.createVm(&config, Some(&log_fd)).context("Failed to create VM")?;
Alan Stokes17fd36a2021-09-06 17:22:37 +010097 let vm_state = Arc::new(VmStateMonitor::default());
98
99 let vm_state_clone = Arc::clone(&vm_state);
100 vm.as_binder().link_to_death(&mut DeathRecipient::new(move || {
101 vm_state_clone.set_died();
102 log::error!("VirtualizationService died");
103 }))?;
104
105 let vm_state_clone = Arc::clone(&vm_state);
106 let callback = BnVirtualMachineCallback::new_binder(
107 VmCallback(vm_state_clone),
108 BinderFeatures::default(),
109 );
110 vm.registerCallback(&callback)?;
111
Andrew Walbranf8d94112021-09-07 11:45:36 +0000112 vm.start()?;
113
Alan Stokesb5c60b42021-09-09 14:44:13 +0100114 let cid = vm_state.wait_until_ready()?;
Alan Stokes17fd36a2021-09-06 17:22:37 +0100115
Alan Stokesf03d81a2021-09-20 17:44:03 +0100116 Ok(VmInstance { service, vm, cid })
Alan Stokes17fd36a2021-09-06 17:22:37 +0100117 }
118
119 /// Create and return an RPC Binder connection to the Comp OS service in the VM.
120 pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
Alan Stokes714fcc22021-09-14 17:43:09 +0100121 let mut vsock_factory = VsockFactory::new(&*self.vm);
Alan Stokes714fcc22021-09-14 17:43:09 +0100122
Alan Stokesf03d81a2021-09-20 17:44:03 +0100123 let ibinder = vsock_factory
124 .connect_rpc_client()
125 .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
Alan Stokes17fd36a2021-09-06 17:22:37 +0100126
127 FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
128 }
Alan Stokesb2cc79e2021-09-14 14:08:46 +0100129
130 /// Return the CID of the VM.
131 pub fn cid(&self) -> i32 {
132 self.cid
133 }
Alan Stokes17fd36a2021-09-06 17:22:37 +0100134}
135
Alan Stokes714fcc22021-09-14 17:43:09 +0100136struct VsockFactory<'a> {
137 vm: &'a dyn IVirtualMachine,
138}
139
140impl<'a> VsockFactory<'a> {
141 fn new(vm: &'a dyn IVirtualMachine) -> Self {
142 Self { vm }
143 }
144
Alan Stokesf03d81a2021-09-20 17:44:03 +0100145 fn connect_rpc_client(&mut self) -> Option<binder::SpIBinder> {
146 let param = self.as_void_ptr();
147
148 unsafe {
149 // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
150 // the ownership can be safely taken by new_spibinder.
151 // RpcPreconnectedClient does not take ownership of param, only passing it to
152 // request_fd.
153 let binder =
154 binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
155 as *mut AIBinder;
156 new_spibinder(binder)
157 }
158 }
159
Alan Stokes714fcc22021-09-14 17:43:09 +0100160 fn as_void_ptr(&mut self) -> *mut raw::c_void {
161 self as *mut _ as *mut raw::c_void
162 }
163
164 fn try_new_vsock_fd(&self) -> Result<i32> {
165 let vsock = self.vm.connectVsock(COMPOS_VSOCK_PORT as i32)?;
Alan Stokes714fcc22021-09-14 17:43:09 +0100166 // Ownership of the fd is transferred to binder
167 Ok(vsock.into_raw_fd())
168 }
169
170 fn new_vsock_fd(&self) -> i32 {
171 self.try_new_vsock_fd().unwrap_or_else(|e| {
172 warn!("Connecting vsock failed: {}", e);
173 -1_i32
174 })
175 }
176
177 unsafe extern "C" fn request_fd(param: *mut raw::c_void) -> raw::c_int {
178 // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
179 // VsockFactory, with param taking the value returned by as_void_ptr (so a properly aligned
180 // non-null pointer to an initialized instance).
Alan Stokesf03d81a2021-09-20 17:44:03 +0100181 let vsock_factory = param as *mut Self;
182 vsock_factory.as_ref().unwrap().new_vsock_fd()
Alan Stokes714fcc22021-09-14 17:43:09 +0100183 }
184}
185
Alan Stokes17fd36a2021-09-06 17:22:37 +0100186#[derive(Debug)]
187struct VmState {
188 has_died: bool,
189 cid: Option<i32>,
190}
191
192impl Default for VmState {
193 fn default() -> Self {
194 Self { has_died: false, cid: None }
195 }
196}
197
198#[derive(Debug)]
199struct VmStateMonitor {
200 mutex: Mutex<VmState>,
201 state_ready: Condvar,
202}
203
204impl Default for VmStateMonitor {
205 fn default() -> Self {
206 Self { mutex: Mutex::new(Default::default()), state_ready: Condvar::new() }
207 }
208}
209
210impl VmStateMonitor {
211 fn set_died(&self) {
212 let mut state = self.mutex.lock().unwrap();
213 state.has_died = true;
214 state.cid = None;
215 drop(state); // Unlock the mutex prior to notifying
216 self.state_ready.notify_all();
217 }
218
Alan Stokesb5c60b42021-09-09 14:44:13 +0100219 fn set_ready(&self, cid: i32) {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100220 let mut state = self.mutex.lock().unwrap();
221 if state.has_died {
222 return;
223 }
224 state.cid = Some(cid);
225 drop(state); // Unlock the mutex prior to notifying
226 self.state_ready.notify_all();
227 }
228
Alan Stokesb5c60b42021-09-09 14:44:13 +0100229 fn wait_until_ready(&self) -> Result<i32> {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100230 let (state, result) = self
231 .state_ready
Alan Stokesb5c60b42021-09-09 14:44:13 +0100232 .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(20), |state| {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100233 state.cid.is_none() && !state.has_died
234 })
235 .unwrap();
236 if result.timed_out() {
237 bail!("Timed out waiting for VM")
238 }
239 state.cid.ok_or_else(|| anyhow!("VM died"))
240 }
241}
242
243#[derive(Debug)]
244struct VmCallback(Arc<VmStateMonitor>);
245
246impl Interface for VmCallback {}
247
248impl IVirtualMachineCallback for VmCallback {
249 fn onDied(&self, cid: i32) -> BinderResult<()> {
250 self.0.set_died();
251 log::warn!("VM died, cid = {}", cid);
252 Ok(())
253 }
254
255 fn onPayloadStarted(
256 &self,
257 cid: i32,
Alan Stokesf03d81a2021-09-20 17:44:03 +0100258 stream: Option<&ParcelFileDescriptor>,
Alan Stokes17fd36a2021-09-06 17:22:37 +0100259 ) -> BinderResult<()> {
Alan Stokesf03d81a2021-09-20 17:44:03 +0100260 if let Some(pfd) = stream {
261 if let Err(e) = start_logging(pfd) {
262 warn!("Can't log vm output: {}", e);
263 };
264 }
Alan Stokes17fd36a2021-09-06 17:22:37 +0100265 log::info!("VM payload started, cid = {}", cid);
266 Ok(())
267 }
268
269 fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
Alan Stokesb5c60b42021-09-09 14:44:13 +0100270 self.0.set_ready(cid);
Alan Stokes17fd36a2021-09-06 17:22:37 +0100271 log::info!("VM payload ready, cid = {}", cid);
272 Ok(())
273 }
274
275 fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
276 // This should probably never happen in our case, but if it does we means our VM is no
277 // longer running
278 self.0.set_died();
279 log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
280 Ok(())
281 }
282}
Alan Stokesf03d81a2021-09-20 17:44:03 +0100283
284fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
285 let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
286 thread::spawn(move || {
287 for line in reader.lines() {
288 match line {
289 Ok(line) => info!("VM: {}", line),
290 Err(e) => {
291 warn!("Reading VM output failed: {}", e);
292 break;
293 }
294 }
295 }
296 });
297 Ok(())
298}