• 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         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::ffi::{c_char, c_int, c_void, CString};
47 use std::io::{self, Read};
48 use std::os::fd::RawFd;
49 use std::process::Command;
50 use std::{
51     fmt::{self, Debug, Formatter},
52     fs::File,
53     os::unix::io::{AsFd, AsRawFd, IntoRawFd, OwnedFd},
54     sync::Arc,
55     time::Duration,
56 };
57 
58 const EARLY_VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/early_virtmgr";
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     Ok(pipe2(OFlag::O_CLOEXEC)?)
69 }
70 
posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error>71 fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
72     use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
73 
74     // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
75     // transport. Make it O_CLOEXEC to align with how Rust creates file
76     // descriptors (expected by SharedChild).
77     Ok(socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?)
78 }
79 
80 /// Error handling function for `get_virtualization_service`.
81 ///
82 /// # Safety
83 /// `message` shouldn't be used outside of the lifetime of the function. Management of `ctx` is
84 /// entirely up to the function.
85 pub type ErrorCallback =
86     unsafe extern "C" fn(code: c_int, message: *const c_char, ctx: *mut c_void);
87 
88 /// Spawns a new instance of virtmgr and rerturns a file descriptor for the socket connection to
89 /// the service. When error occurs, it is reported via the ErrorCallback function along with the
90 /// error message and any context that is set by the client.
91 ///
92 /// # Safety
93 /// `cb` should be null or a valid function pointer of type `ErrorCallback`
94 #[no_mangle]
get_virtualization_service( cb: Option<ErrorCallback>, ctx: *mut c_void, ) -> RawFd95 pub unsafe extern "C" fn get_virtualization_service(
96     cb: Option<ErrorCallback>,
97     ctx: *mut c_void,
98 ) -> RawFd {
99     match VirtualizationService::new() {
100         Ok(vs) => vs.client_fd.into_raw_fd(),
101         Err(e) => {
102             if let Some(cb) = cb {
103                 let code = e.raw_os_error().unwrap_or(-1);
104                 let msg = CString::new(e.to_string()).unwrap();
105                 // SAFETY: `cb` doesn't use `msg` outside of the lifetime of the function.
106                 // msg's lifetime is longer than `cb` as it is bound to a local variable.
107                 unsafe { cb(code, msg.as_ptr(), ctx) };
108             }
109             -1
110         }
111     }
112 }
113 
114 /// A running instance of virtmgr which is hosting a VirtualizationService
115 /// RpcBinder server.
116 pub struct VirtualizationService {
117     /// Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
118     /// will make virtmgr shut down.
119     client_fd: OwnedFd,
120 }
121 
122 impl VirtualizationService {
123     /// Spawns a new instance of virtmgr, a child process that will host
124     /// the VirtualizationService AIDL service.
new() -> Result<VirtualizationService, io::Error>125     pub fn new() -> Result<VirtualizationService, io::Error> {
126         Self::new_with_path(VIRTMGR_PATH)
127     }
128 
129     /// Spawns a new instance of early_virtmgr, a child process that will host
130     /// the VirtualizationService AIDL service for early VMs.
new_early() -> Result<VirtualizationService, io::Error>131     pub fn new_early() -> Result<VirtualizationService, io::Error> {
132         Self::new_with_path(EARLY_VIRTMGR_PATH)
133     }
134 
new_with_path(virtmgr_path: &str) -> Result<VirtualizationService, io::Error>135     fn new_with_path(virtmgr_path: &str) -> Result<VirtualizationService, io::Error> {
136         let (wait_fd, ready_fd) = posix_pipe()?;
137         let (client_fd, server_fd) = posix_socketpair()?;
138 
139         let mut command = Command::new(virtmgr_path);
140         // Can't use BorrowedFd as it doesn't implement Display
141         command.arg("--rpc-server-fd").arg(format!("{}", server_fd.as_raw_fd()));
142         command.arg("--ready-fd").arg(format!("{}", ready_fd.as_raw_fd()));
143         command.preserved_fds(vec![server_fd, ready_fd]);
144 
145         SharedChild::spawn(&mut command)?;
146 
147         // Wait for the child to signal that the RpcBinder server is read by closing its end of the
148         // pipe. Failing to read (especially EACCESS or EPERM) can happen if the client lacks the
149         // MANAGE_VIRTUAL_MACHINE permission. Therefore, such errors are propagated instead of
150         // being ignored.
151         let _ = File::from(wait_fd).read(&mut [0])?;
152         Ok(VirtualizationService { client_fd })
153     }
154 
155     /// Connects to the VirtualizationService AIDL service.
connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error>156     pub fn connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error> {
157         let session = RpcSession::new();
158         session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
159         session.set_max_incoming_threads(VIRTMGR_THREADS);
160         session
161             .setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
162             .map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))
163     }
164 }
165 
166 /// A virtual machine which has been started by the VirtualizationService.
167 pub struct VmInstance {
168     /// The `IVirtualMachine` Binder object representing the VM.
169     pub vm: Strong<dyn IVirtualMachine>,
170     cid: i32,
171     state: Arc<Monitor<VmState>>,
172     // Ensure that the DeathRecipient isn't dropped while someone might call wait_for_death, as it
173     // is removed from the Binder when it's dropped.
174     _death_recipient: DeathRecipient,
175 }
176 
177 /// A trait to be implemented by clients to handle notification of significant changes to the VM
178 /// state. Default implementations of all functions are provided so clients only need to handle the
179 /// notifications they are interested in.
180 #[allow(unused_variables)]
181 pub trait VmCallback {
182     /// Called when the payload has been started within the VM. If present, `stream` is connected
183     /// to the stdin/stdout of the payload.
on_payload_started(&self, cid: i32)184     fn on_payload_started(&self, cid: i32) {}
185 
186     /// Callend when the payload has notified Virtualization Service that it is ready to serve
187     /// clients.
on_payload_ready(&self, cid: i32)188     fn on_payload_ready(&self, cid: i32) {}
189 
190     /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
191     /// process.
on_payload_finished(&self, cid: i32, exit_code: i32)192     fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
193 
194     /// Called when an error has occurred in the VM. The `error_code` and `message` may give
195     /// further details.
on_error(&self, cid: i32, error_code: ErrorCode, message: &str)196     fn on_error(&self, cid: i32, error_code: ErrorCode, message: &str) {}
197 
198     /// Called when the VM has exited, all resources have been freed, and any logs have been
199     /// written. `death_reason` gives an indication why the VM exited.
on_died(&self, cid: i32, death_reason: DeathReason)200     fn on_died(&self, cid: i32, death_reason: DeathReason) {}
201 }
202 
203 impl VmInstance {
204     /// 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>, dump_dt: Option<File>, ) -> BinderResult<Self>205     pub fn create(
206         service: &dyn IVirtualizationService,
207         config: &VirtualMachineConfig,
208         console_out: Option<File>,
209         console_in: Option<File>,
210         log: Option<File>,
211         dump_dt: Option<File>,
212     ) -> BinderResult<Self> {
213         let console_out = console_out.map(ParcelFileDescriptor::new);
214         let console_in = console_in.map(ParcelFileDescriptor::new);
215         let log = log.map(ParcelFileDescriptor::new);
216         let dump_dt = dump_dt.map(ParcelFileDescriptor::new);
217 
218         let vm = service.createVm(
219             config,
220             console_out.as_ref(),
221             console_in.as_ref(),
222             log.as_ref(),
223             dump_dt.as_ref(),
224         )?;
225 
226         let cid = vm.getCid()?;
227 
228         let state = Arc::new(Monitor::new(VmState::default()));
229         let death_recipient = wait_for_binder_death(&mut vm.as_binder(), state.clone())?;
230 
231         Ok(Self { vm, cid, state, _death_recipient: death_recipient })
232     }
233 
234     /// Starts the VM.
start(&self, callback: Option<Box<dyn VmCallback + Send + Sync>>) -> BinderResult<()>235     pub fn start(&self, callback: Option<Box<dyn VmCallback + Send + Sync>>) -> BinderResult<()> {
236         let callback = BnVirtualMachineCallback::new_binder(
237             VirtualMachineCallback { state: self.state.clone(), client_callback: callback },
238             BinderFeatures::default(),
239         );
240         self.vm.registerCallback(&callback)?;
241         self.vm.start()
242     }
243 
244     /// Stops the VM.
stop(&self) -> BinderResult<()>245     pub fn stop(&self) -> BinderResult<()> {
246         self.vm.stop()
247     }
248 
249     /// Returns the CID used for vsock connections to the VM.
cid(&self) -> i32250     pub fn cid(&self) -> i32 {
251         self.cid
252     }
253 
254     /// Returns the current lifecycle state of the VM.
state(&self) -> BinderResult<VirtualMachineState>255     pub fn state(&self) -> BinderResult<VirtualMachineState> {
256         self.vm.getState()
257     }
258 
259     /// Blocks until the VM or the VirtualizationService itself dies, and then returns the reason
260     /// why it died.
wait_for_death(&self) -> DeathReason261     pub fn wait_for_death(&self) -> DeathReason {
262         self.state.wait_while(|state| state.death_reason.is_none()).unwrap().death_reason.unwrap()
263     }
264 
265     /// Blocks until the VM or the VirtualizationService itself dies, or the given timeout expires.
266     /// Returns the reason why it died if it did so.
wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason>267     pub fn wait_for_death_with_timeout(&self, timeout: Duration) -> Option<DeathReason> {
268         let (state, _timeout_result) =
269             self.state.wait_timeout_while(timeout, |state| state.death_reason.is_none()).unwrap();
270         // We don't care if it timed out - we just return the reason if there now is one
271         state.death_reason
272     }
273 
274     /// Waits until the VM reports that it is ready.
275     ///
276     /// 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>277     pub fn wait_until_ready(&self, timeout: Duration) -> Result<(), VmWaitError> {
278         let (state, timeout_result) = self
279             .state
280             .wait_timeout_while(timeout, |state| {
281                 state.reported_state < VirtualMachineState::READY && state.death_reason.is_none()
282             })
283             .unwrap();
284         if timeout_result.timed_out() {
285             Err(VmWaitError::TimedOut)
286         } else if let Some(reason) = state.death_reason {
287             Err(VmWaitError::Died { reason })
288         } else if state.reported_state != VirtualMachineState::READY {
289             Err(VmWaitError::Finished)
290         } else {
291             Ok(())
292         }
293     }
294 
295     /// 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>296     pub fn connect_service<T: FromIBinder + ?Sized>(
297         &self,
298         port: u32,
299     ) -> Result<Strong<T>, StatusCode> {
300         RpcSession::new().setup_preconnected_client(|| {
301             match self.vm.connectVsock(port as i32) {
302                 Ok(vsock) => {
303                     // Ownership of the fd is transferred to binder
304                     Some(vsock.into_raw_fd())
305                 }
306                 Err(e) => {
307                     warn!("Vsock connection failed: {}", e);
308                     None
309                 }
310             }
311         })
312     }
313 
314     /// Opens a vsock connection to the CID of the VM on the given vsock port.
connect_vsock(&self, port: u32) -> BinderResult<ParcelFileDescriptor>315     pub fn connect_vsock(&self, port: u32) -> BinderResult<ParcelFileDescriptor> {
316         self.vm.connectVsock(port as i32)
317     }
318 }
319 
320 impl Debug for VmInstance {
fmt(&self, f: &mut Formatter) -> fmt::Result321     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
322         f.debug_struct("VmInstance").field("cid", &self.cid).field("state", &self.state).finish()
323     }
324 }
325 
326 /// Notify the VmState when the given Binder object dies.
327 ///
328 /// 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>329 fn wait_for_binder_death(
330     binder: &mut impl IBinder,
331     state: Arc<Monitor<VmState>>,
332 ) -> BinderResult<DeathRecipient> {
333     let mut death_recipient = DeathRecipient::new(move || {
334         warn!("VirtualizationService unexpectedly died");
335         state.notify_death(DeathReason::VirtualizationServiceDied);
336     });
337     binder.link_to_death(&mut death_recipient)?;
338     Ok(death_recipient)
339 }
340 
341 #[derive(Debug, Default)]
342 struct VmState {
343     death_reason: Option<DeathReason>,
344     reported_state: VirtualMachineState,
345 }
346 
347 impl Monitor<VmState> {
notify_death(&self, reason: DeathReason)348     fn notify_death(&self, reason: DeathReason) {
349         let state = &mut *self.state.lock().unwrap();
350         // In case this method is called more than once, ignore subsequent calls.
351         if state.death_reason.is_none() {
352             state.death_reason.replace(reason);
353             self.cv.notify_all();
354         }
355     }
356 
notify_state(&self, state: VirtualMachineState)357     fn notify_state(&self, state: VirtualMachineState) {
358         self.state.lock().unwrap().reported_state = state;
359         self.cv.notify_all();
360     }
361 }
362 
363 struct VirtualMachineCallback {
364     state: Arc<Monitor<VmState>>,
365     client_callback: Option<Box<dyn VmCallback + Send + Sync>>,
366 }
367 
368 impl Debug for VirtualMachineCallback {
fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result369     fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
370         fmt.debug_struct("VirtualMachineCallback")
371             .field("state", &self.state)
372             .field(
373                 "client_callback",
374                 &if self.client_callback.is_some() { "Some(...)" } else { "None" },
375             )
376             .finish()
377     }
378 }
379 
380 impl Interface for VirtualMachineCallback {}
381 
382 impl IVirtualMachineCallback for VirtualMachineCallback {
onPayloadStarted(&self, cid: i32) -> BinderResult<()>383     fn onPayloadStarted(&self, cid: i32) -> BinderResult<()> {
384         self.state.notify_state(VirtualMachineState::STARTED);
385         if let Some(ref callback) = self.client_callback {
386             callback.on_payload_started(cid);
387         }
388         Ok(())
389     }
390 
onPayloadReady(&self, cid: i32) -> BinderResult<()>391     fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
392         self.state.notify_state(VirtualMachineState::READY);
393         if let Some(ref callback) = self.client_callback {
394             callback.on_payload_ready(cid);
395         }
396         Ok(())
397     }
398 
onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()>399     fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
400         self.state.notify_state(VirtualMachineState::FINISHED);
401         if let Some(ref callback) = self.client_callback {
402             callback.on_payload_finished(cid, exit_code);
403         }
404         Ok(())
405     }
406 
onError(&self, cid: i32, error_code: AidlErrorCode, message: &str) -> BinderResult<()>407     fn onError(&self, cid: i32, error_code: AidlErrorCode, message: &str) -> BinderResult<()> {
408         self.state.notify_state(VirtualMachineState::FINISHED);
409         if let Some(ref callback) = self.client_callback {
410             let error_code = error_code.into();
411             callback.on_error(cid, error_code, message);
412         }
413         Ok(())
414     }
415 
onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()>416     fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
417         let reason = reason.into();
418         self.state.notify_death(reason);
419         if let Some(ref callback) = self.client_callback {
420             callback.on_died(cid, reason);
421         }
422         Ok(())
423     }
424 }
425