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