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