blob: c525f4030e9466bdf92ec126a28bdc78eca5f572 [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>,
53 cid: i32,
54}
55
56impl VmInstance {
57 /// Start a new CompOS VM instance using the specified instance image file.
58 pub fn start(instance_image: &Path) -> Result<VmInstance> {
59 let instance_image =
60 File::open(instance_image).context("Failed to open instance image file")?;
61 let instance_fd = ParcelFileDescriptor::new(instance_image);
62
63 let apex_dir = Path::new(COMPOS_APEX_ROOT);
64 let data_dir = Path::new(COMPOS_DATA_ROOT);
65
66 let apk_fd = File::open(apex_dir.join("app/CompOSPayloadApp/CompOSPayloadApp.apk"))
67 .context("Failed to open config APK file")?;
68 let apk_fd = ParcelFileDescriptor::new(apk_fd);
69
70 let idsig_fd = File::open(apex_dir.join("etc/CompOSPayloadApp.apk.idsig"))
71 .context("Failed to open config APK idsig file")?;
72 let idsig_fd = ParcelFileDescriptor::new(idsig_fd);
73
74 // TODO: Send this to stdout instead? Or specify None?
75 let log_fd = File::create(data_dir.join("vm.log")).context("Failed to create log file")?;
76 let log_fd = ParcelFileDescriptor::new(log_fd);
77
78 let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
79 apk: Some(apk_fd),
80 idsig: Some(idsig_fd),
81 instanceImage: Some(instance_fd),
82 configPath: "assets/vm_config.json".to_owned(),
83 ..Default::default()
84 });
85
86 let service = wait_for_interface::<dyn IVirtualizationService>(
87 "android.system.virtualizationservice",
88 )
89 .context("Failed to find VirtualizationService")?;
90
Andrew Walbranf8d94112021-09-07 11:45:36 +000091 let vm = service.createVm(&config, Some(&log_fd)).context("Failed to create VM")?;
Alan Stokes17fd36a2021-09-06 17:22:37 +010092 let vm_state = Arc::new(VmStateMonitor::default());
93
94 let vm_state_clone = Arc::clone(&vm_state);
95 vm.as_binder().link_to_death(&mut DeathRecipient::new(move || {
96 vm_state_clone.set_died();
97 log::error!("VirtualizationService died");
98 }))?;
99
100 let vm_state_clone = Arc::clone(&vm_state);
101 let callback = BnVirtualMachineCallback::new_binder(
102 VmCallback(vm_state_clone),
103 BinderFeatures::default(),
104 );
105 vm.registerCallback(&callback)?;
106
Andrew Walbranf8d94112021-09-07 11:45:36 +0000107 vm.start()?;
108
Alan Stokesb5c60b42021-09-09 14:44:13 +0100109 let cid = vm_state.wait_until_ready()?;
Alan Stokes17fd36a2021-09-06 17:22:37 +0100110
Alan Stokesf03d81a2021-09-20 17:44:03 +0100111 Ok(VmInstance { service, vm, cid })
Alan Stokes17fd36a2021-09-06 17:22:37 +0100112 }
113
114 /// Create and return an RPC Binder connection to the Comp OS service in the VM.
115 pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
Alan Stokes714fcc22021-09-14 17:43:09 +0100116 let mut vsock_factory = VsockFactory::new(&*self.vm);
Alan Stokes714fcc22021-09-14 17:43:09 +0100117
Alan Stokesf03d81a2021-09-20 17:44:03 +0100118 let ibinder = vsock_factory
119 .connect_rpc_client()
120 .ok_or_else(|| anyhow!("Failed to connect to CompOS service"))?;
Alan Stokes17fd36a2021-09-06 17:22:37 +0100121
122 FromIBinder::try_from(ibinder).context("Connecting to CompOS service")
123 }
Alan Stokesb2cc79e2021-09-14 14:08:46 +0100124
125 /// Return the CID of the VM.
126 pub fn cid(&self) -> i32 {
127 self.cid
128 }
Alan Stokes17fd36a2021-09-06 17:22:37 +0100129}
130
Alan Stokes714fcc22021-09-14 17:43:09 +0100131struct VsockFactory<'a> {
132 vm: &'a dyn IVirtualMachine,
133}
134
135impl<'a> VsockFactory<'a> {
136 fn new(vm: &'a dyn IVirtualMachine) -> Self {
137 Self { vm }
138 }
139
Alan Stokesf03d81a2021-09-20 17:44:03 +0100140 fn connect_rpc_client(&mut self) -> Option<binder::SpIBinder> {
141 let param = self.as_void_ptr();
142
143 unsafe {
144 // SAFETY: AIBinder returned by RpcPreconnectedClient has correct reference count, and
145 // the ownership can be safely taken by new_spibinder.
146 // RpcPreconnectedClient does not take ownership of param, only passing it to
147 // request_fd.
148 let binder =
149 binder_rpc_unstable_bindgen::RpcPreconnectedClient(Some(Self::request_fd), param)
150 as *mut AIBinder;
151 new_spibinder(binder)
152 }
153 }
154
Alan Stokes714fcc22021-09-14 17:43:09 +0100155 fn as_void_ptr(&mut self) -> *mut raw::c_void {
156 self as *mut _ as *mut raw::c_void
157 }
158
159 fn try_new_vsock_fd(&self) -> Result<i32> {
160 let vsock = self.vm.connectVsock(COMPOS_VSOCK_PORT as i32)?;
Alan Stokes714fcc22021-09-14 17:43:09 +0100161 // Ownership of the fd is transferred to binder
162 Ok(vsock.into_raw_fd())
163 }
164
165 fn new_vsock_fd(&self) -> i32 {
166 self.try_new_vsock_fd().unwrap_or_else(|e| {
167 warn!("Connecting vsock failed: {}", e);
168 -1_i32
169 })
170 }
171
172 unsafe extern "C" fn request_fd(param: *mut raw::c_void) -> raw::c_int {
173 // SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
174 // VsockFactory, with param taking the value returned by as_void_ptr (so a properly aligned
175 // non-null pointer to an initialized instance).
Alan Stokesf03d81a2021-09-20 17:44:03 +0100176 let vsock_factory = param as *mut Self;
177 vsock_factory.as_ref().unwrap().new_vsock_fd()
Alan Stokes714fcc22021-09-14 17:43:09 +0100178 }
179}
180
Alan Stokes17fd36a2021-09-06 17:22:37 +0100181#[derive(Debug)]
182struct VmState {
183 has_died: bool,
184 cid: Option<i32>,
185}
186
187impl Default for VmState {
188 fn default() -> Self {
189 Self { has_died: false, cid: None }
190 }
191}
192
193#[derive(Debug)]
194struct VmStateMonitor {
195 mutex: Mutex<VmState>,
196 state_ready: Condvar,
197}
198
199impl Default for VmStateMonitor {
200 fn default() -> Self {
201 Self { mutex: Mutex::new(Default::default()), state_ready: Condvar::new() }
202 }
203}
204
205impl VmStateMonitor {
206 fn set_died(&self) {
207 let mut state = self.mutex.lock().unwrap();
208 state.has_died = true;
209 state.cid = None;
210 drop(state); // Unlock the mutex prior to notifying
211 self.state_ready.notify_all();
212 }
213
Alan Stokesb5c60b42021-09-09 14:44:13 +0100214 fn set_ready(&self, cid: i32) {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100215 let mut state = self.mutex.lock().unwrap();
216 if state.has_died {
217 return;
218 }
219 state.cid = Some(cid);
220 drop(state); // Unlock the mutex prior to notifying
221 self.state_ready.notify_all();
222 }
223
Alan Stokesb5c60b42021-09-09 14:44:13 +0100224 fn wait_until_ready(&self) -> Result<i32> {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100225 let (state, result) = self
226 .state_ready
Alan Stokesb5c60b42021-09-09 14:44:13 +0100227 .wait_timeout_while(self.mutex.lock().unwrap(), Duration::from_secs(20), |state| {
Alan Stokes17fd36a2021-09-06 17:22:37 +0100228 state.cid.is_none() && !state.has_died
229 })
230 .unwrap();
231 if result.timed_out() {
232 bail!("Timed out waiting for VM")
233 }
234 state.cid.ok_or_else(|| anyhow!("VM died"))
235 }
236}
237
238#[derive(Debug)]
239struct VmCallback(Arc<VmStateMonitor>);
240
241impl Interface for VmCallback {}
242
243impl IVirtualMachineCallback for VmCallback {
244 fn onDied(&self, cid: i32) -> BinderResult<()> {
245 self.0.set_died();
246 log::warn!("VM died, cid = {}", cid);
247 Ok(())
248 }
249
250 fn onPayloadStarted(
251 &self,
252 cid: i32,
Alan Stokesf03d81a2021-09-20 17:44:03 +0100253 stream: Option<&ParcelFileDescriptor>,
Alan Stokes17fd36a2021-09-06 17:22:37 +0100254 ) -> BinderResult<()> {
Alan Stokesf03d81a2021-09-20 17:44:03 +0100255 if let Some(pfd) = stream {
256 if let Err(e) = start_logging(pfd) {
257 warn!("Can't log vm output: {}", e);
258 };
259 }
Alan Stokes17fd36a2021-09-06 17:22:37 +0100260 log::info!("VM payload started, cid = {}", cid);
261 Ok(())
262 }
263
264 fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
Alan Stokesb5c60b42021-09-09 14:44:13 +0100265 self.0.set_ready(cid);
Alan Stokes17fd36a2021-09-06 17:22:37 +0100266 log::info!("VM payload ready, cid = {}", cid);
267 Ok(())
268 }
269
270 fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
271 // This should probably never happen in our case, but if it does we means our VM is no
272 // longer running
273 self.0.set_died();
274 log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
275 Ok(())
276 }
277}
Alan Stokesf03d81a2021-09-20 17:44:03 +0100278
279fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
280 let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
281 thread::spawn(move || {
282 for line in reader.lines() {
283 match line {
284 Ok(line) => info!("VM: {}", line),
285 Err(e) => {
286 warn!("Reading VM output failed: {}", e);
287 break;
288 }
289 }
290 }
291 });
292 Ok(())
293}