1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! Client library for VirtualizationService.
16
17 mod death_reason;
18 mod error_code;
19 mod errors;
20 mod sync;
21
22 pub use crate::death_reason::DeathReason;
23 pub use crate::error_code::ErrorCode;
24 pub use crate::errors::VmWaitError;
25 use crate::sync::Monitor;
26 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::{
27 DeathReason::DeathReason as AidlDeathReason, ErrorCode::ErrorCode as AidlErrorCode,
28 };
29 use android_system_virtualizationservice::{
30 aidl::android::system::virtualizationservice::{
31 IVirtualMachine::IVirtualMachine,
32 IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
33 IVirtualizationService::IVirtualizationService,
34 VirtualMachineConfig::VirtualMachineConfig,
35 VirtualMachineState::VirtualMachineState,
36 },
37 binder::{
38 BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, ParcelFileDescriptor,
39 Result as BinderResult, StatusCode, Strong,
40 },
41 };
42 use command_fds::CommandFdExt;
43 use log::warn;
44 use rpcbinder::{FileDescriptorTransportMode, RpcSession};
45 use shared_child::SharedChild;
46 use std::io::{self, Read};
47 use std::process::Command;
48 use std::{
49 fmt::{self, Debug, Formatter},
50 fs::File,
51 os::unix::io::{AsFd, AsRawFd, IntoRawFd, OwnedFd},
52 sync::Arc,
53 time::Duration,
54 };
55
56 const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
57 const VIRTMGR_THREADS: usize = 2;
58
posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error>59 fn posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error> {
60 use nix::fcntl::OFlag;
61 use nix::unistd::pipe2;
62
63 // Create new POSIX pipe. Make it O_CLOEXEC to align with how Rust creates
64 // file descriptors (expected by SharedChild).
65 Ok(pipe2(OFlag::O_CLOEXEC)?)
66 }
67
posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error>68 fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
69 use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
70
71 // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
72 // transport. Make it O_CLOEXEC to align with how Rust creates file
73 // descriptors (expected by SharedChild).
74 Ok(socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?)
75 }
76
77 /// A running instance of virtmgr which is hosting a VirtualizationService
78 /// RpcBinder server.
79 pub struct VirtualizationService {
80 /// Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
81 /// will make virtmgr shut down.
82 client_fd: OwnedFd,
83 }
84
85 impl VirtualizationService {
86 /// Spawns a new instance of virtmgr, a child process that will host
87 /// the VirtualizationService AIDL service.
new() -> Result<VirtualizationService, io::Error>88 pub fn new() -> Result<VirtualizationService, io::Error> {
89 let (wait_fd, ready_fd) = posix_pipe()?;
90 let (client_fd, server_fd) = posix_socketpair()?;
91
92 let mut command = Command::new(VIRTMGR_PATH);
93 command.arg("--rpc-server-fd").arg(format!("{}", server_fd.as_raw_fd()));
94 command.arg("--ready-fd").arg(format!("{}", ready_fd.as_raw_fd()));
95 command.preserved_fds(vec![server_fd.as_raw_fd(), ready_fd.as_raw_fd()]);
96
97 SharedChild::spawn(&mut command)?;
98
99 // Drop FDs that belong to virtmgr.
100 drop(server_fd);
101 drop(ready_fd);
102
103 // Wait for the child to signal that the RpcBinder server is ready
104 // by closing its end of the pipe.
105 let _ignored = File::from(wait_fd).read(&mut [0]);
106
107 Ok(VirtualizationService { client_fd })
108 }
109
110 /// Connects to the VirtualizationService AIDL service.
connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error>111 pub fn connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error> {
112 let session = RpcSession::new();
113 session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
114 session.set_max_incoming_threads(VIRTMGR_THREADS);
115 session
116 .setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
117 .map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))
118 }
119 }
120
121 /// A virtual machine which has been started by the VirtualizationService.
122 pub struct VmInstance {
123 /// The `IVirtualMachine` Binder object representing the VM.
124 pub vm: Strong<dyn IVirtualMachine>,
125 cid: i32,
126 state: Arc<Monitor<VmState>>,
127 // Ensure that the DeathRecipient isn't dropped while someone might call wait_for_death, as it
128 // is removed from the Binder when it's dropped.
129 _death_recipient: DeathRecipient,
130 }
131
132 /// A trait to be implemented by clients to handle notification of significant changes to the VM
133 /// state. Default implementations of all functions are provided so clients only need to handle the
134 /// notifications they are interested in.
135 #[allow(unused_variables)]
136 pub trait VmCallback {
137 /// Called when the payload has been started within the VM. If present, `stream` is connected
138 /// to the stdin/stdout of the payload.
on_payload_started(&self, cid: i32)139 fn on_payload_started(&self, cid: i32) {}
140
141 /// Callend when the payload has notified Virtualization Service that it is ready to serve
142 /// clients.
on_payload_ready(&self, cid: i32)143 fn on_payload_ready(&self, cid: i32) {}
144
145 /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
146 /// process.
on_payload_finished(&self, cid: i32, exit_code: i32)147 fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
148
149 /// Called when an error has occurred in the VM. The `error_code` and `message` may give
150 /// further details.
on_error(&self, cid: i32, error_code: ErrorCode, message: &str)151 fn on_error(&self, cid: i32, error_code: ErrorCode, message: &str) {}
152
153 /// Called when the VM has exited, all resources have been freed, and any logs have been
154 /// written. `death_reason` gives an indication why the VM exited.
on_died(&self, cid: i32, death_reason: DeathReason)155 fn on_died(&self, cid: i32, death_reason: DeathReason) {}
156 }
157
158 impl VmInstance {
159 /// Creates (but doesn't start) a new VM with the given configuration.
create( service: &dyn IVirtualizationService, config: &VirtualMachineConfig, console_out: Option<File>, console_in: Option<File>, log: Option<File>, callback: Option<Box<dyn VmCallback + Send + Sync>>, ) -> BinderResult<Self>160 pub fn create(
161 service: &dyn IVirtualizationService,
162 config: &VirtualMachineConfig,
163 console_out: Option<File>,
164 console_in: Option<File>,
165 log: Option<File>,
166 callback: Option<Box<dyn VmCallback + Send + Sync>>,
167 ) -> BinderResult<Self> {
168 let console_out = console_out.map(ParcelFileDescriptor::new);
169 let console_in = console_in.map(ParcelFileDescriptor::new);
170 let log = log.map(ParcelFileDescriptor::new);
171
172 let vm =
173 service.createVm(config, console_out.as_ref(), console_in.as_ref(), log.as_ref())?;
174
175 let cid = vm.getCid()?;
176
177 // Register callback before starting VM, in case it dies immediately.
178 let state = Arc::new(Monitor::new(VmState::default()));
179 let callback = BnVirtualMachineCallback::new_binder(
180 VirtualMachineCallback { state: state.clone(), client_callback: callback },
181 BinderFeatures::default(),
182 );
183 vm.registerCallback(&callback)?;
184 let death_recipient = wait_for_binder_death(&mut vm.as_binder(), state.clone())?;
185
186 Ok(Self { vm, cid, state, _death_recipient: death_recipient })
187 }
188
189 /// Starts the VM.
start(&self) -> BinderResult<()>190 pub fn start(&self) -> BinderResult<()> {
191 self.vm.start()
192 }
193
194 /// Returns the CID used for vsock connections to the VM.
cid(&self) -> i32195 pub fn cid(&self) -> i32 {
196 self.cid
197 }
198
199 /// Returns the current lifecycle state of the VM.
state(&self) -> BinderResult<VirtualMachineState>200 pub fn state(&self) -> BinderResult<VirtualMachineState> {
201 self.vm.getState()
202 }
203
204 /// Blocks until the VM or the VirtualizationService itself dies, and then returns the reason
205 /// why it died.
wait_for_death(&self) -> DeathReason206 pub fn wait_for_death(&self) -> DeathReason {
207 self.state.wait_while(|state| state.death_reason.is_none()).unwrap().death_reason.unwrap()
208 }
209
210 /// Blocks until the VM or the VirtualizationService itself dies, or the given timeout expires.
211 /// Returns the reason why it died if it did so.
wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason>212 pub fn wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason> {
213 let (state, _timeout_result) =
214 self.state.wait_timeout_while(timeout, |state| state.death_reason.is_none()).unwrap();
215 // We don't care if it timed out - we just return the reason if there now is one
216 state.death_reason
217 }
218
219 /// Waits until the VM reports that it is ready.
220 ///
221 /// Returns an error if the VM dies first, or the `timeout` elapses before the VM is ready.
wait_until_ready(&self, timeout: Duration) -> Result<(), VmWaitError>222 pub fn wait_until_ready(&self, timeout: Duration) -> Result<(), VmWaitError> {
223 let (state, timeout_result) = self
224 .state
225 .wait_timeout_while(timeout, |state| {
226 state.reported_state < VirtualMachineState::READY && state.death_reason.is_none()
227 })
228 .unwrap();
229 if timeout_result.timed_out() {
230 Err(VmWaitError::TimedOut)
231 } else if let Some(reason) = state.death_reason {
232 Err(VmWaitError::Died { reason })
233 } else if state.reported_state != VirtualMachineState::READY {
234 Err(VmWaitError::Finished)
235 } else {
236 Ok(())
237 }
238 }
239
240 /// Tries to connect to an RPC Binder service provided by the VM on the given vsock port.
connect_service<T: FromIBinder + ?Sized>( &self, port: u32, ) -> Result<Strong<T>, StatusCode>241 pub fn connect_service<T: FromIBinder + ?Sized>(
242 &self,
243 port: u32,
244 ) -> Result<Strong<T>, StatusCode> {
245 RpcSession::new().setup_preconnected_client(|| {
246 match self.vm.connectVsock(port as i32) {
247 Ok(vsock) => {
248 // Ownership of the fd is transferred to binder
249 Some(vsock.into_raw_fd())
250 }
251 Err(e) => {
252 warn!("Vsock connection failed: {}", e);
253 None
254 }
255 }
256 })
257 }
258 }
259
260 impl Debug for VmInstance {
fmt(&self, f: &mut Formatter) -> fmt::Result261 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
262 f.debug_struct("VmInstance").field("cid", &self.cid).field("state", &self.state).finish()
263 }
264 }
265
266 /// Notify the VmState when the given Binder object dies.
267 ///
268 /// If the returned DeathRecipient is dropped then this will no longer do anything.
wait_for_binder_death( binder: &mut impl IBinder, state: Arc<Monitor<VmState>>, ) -> BinderResult<DeathRecipient>269 fn wait_for_binder_death(
270 binder: &mut impl IBinder,
271 state: Arc<Monitor<VmState>>,
272 ) -> BinderResult<DeathRecipient> {
273 let mut death_recipient = DeathRecipient::new(move || {
274 warn!("VirtualizationService unexpectedly died");
275 state.notify_death(DeathReason::VirtualizationServiceDied);
276 });
277 binder.link_to_death(&mut death_recipient)?;
278 Ok(death_recipient)
279 }
280
281 #[derive(Debug, Default)]
282 struct VmState {
283 death_reason: Option<DeathReason>,
284 reported_state: VirtualMachineState,
285 }
286
287 impl Monitor<VmState> {
notify_death(&self, reason: DeathReason)288 fn notify_death(&self, reason: DeathReason) {
289 let state = &mut *self.state.lock().unwrap();
290 // In case this method is called more than once, ignore subsequent calls.
291 if state.death_reason.is_none() {
292 state.death_reason.replace(reason);
293 self.cv.notify_all();
294 }
295 }
296
notify_state(&self, state: VirtualMachineState)297 fn notify_state(&self, state: VirtualMachineState) {
298 self.state.lock().unwrap().reported_state = state;
299 self.cv.notify_all();
300 }
301 }
302
303 struct VirtualMachineCallback {
304 state: Arc<Monitor<VmState>>,
305 client_callback: Option<Box<dyn VmCallback + Send + Sync>>,
306 }
307
308 impl Debug for VirtualMachineCallback {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result309 fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
310 fmt.debug_struct("VirtualMachineCallback")
311 .field("state", &self.state)
312 .field(
313 "client_callback",
314 &if self.client_callback.is_some() { "Some(...)" } else { "None" },
315 )
316 .finish()
317 }
318 }
319
320 impl Interface for VirtualMachineCallback {}
321
322 impl IVirtualMachineCallback for VirtualMachineCallback {
onPayloadStarted(&self, cid: i32) -> BinderResult<()>323 fn onPayloadStarted(&self, cid: i32) -> BinderResult<()> {
324 self.state.notify_state(VirtualMachineState::STARTED);
325 if let Some(ref callback) = self.client_callback {
326 callback.on_payload_started(cid);
327 }
328 Ok(())
329 }
330
onPayloadReady(&self, cid: i32) -> BinderResult<()>331 fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
332 self.state.notify_state(VirtualMachineState::READY);
333 if let Some(ref callback) = self.client_callback {
334 callback.on_payload_ready(cid);
335 }
336 Ok(())
337 }
338
onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()>339 fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
340 self.state.notify_state(VirtualMachineState::FINISHED);
341 if let Some(ref callback) = self.client_callback {
342 callback.on_payload_finished(cid, exit_code);
343 }
344 Ok(())
345 }
346
onError(&self, cid: i32, error_code: AidlErrorCode, message: &str) -> BinderResult<()>347 fn onError(&self, cid: i32, error_code: AidlErrorCode, message: &str) -> BinderResult<()> {
348 self.state.notify_state(VirtualMachineState::FINISHED);
349 if let Some(ref callback) = self.client_callback {
350 let error_code = error_code.into();
351 callback.on_error(cid, error_code, message);
352 }
353 Ok(())
354 }
355
onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()>356 fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
357 let reason = reason.into();
358 self.state.notify_death(reason);
359 if let Some(ref callback) = self.client_callback {
360 callback.on_died(cid, reason);
361 }
362 Ok(())
363 }
364 }
365