| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Provides parts of crosvm as a library to communicate with running crosvm instances. |
| //! |
| //! This crate is a programmatic alternative to invoking crosvm with subcommands that produce the |
| //! result on stdout. |
| //! |
| //! Downstream projects rely on this library maintaining a stable API surface. |
| //! Do not make changes to this library without consulting the crosvm externalization team. |
| //! Email: <crosvm-dev@chromium.org> |
| //! |
| //! The API of this library should remain the same regardless of which crosvm features are enabled. |
| //! Any missing functionality should be handled by returning an error at runtime, not conditional |
| //! compilation, so that users can rely on the the same set of functions with the same prototypes |
| //! regardless of how crosvm is configured. |
| //! |
| //! For more information see: |
| //! <https://crosvm.dev/book/running_crosvm/programmatic_interaction.html#usage> |
| |
| use std::convert::TryFrom; |
| use std::convert::TryInto; |
| use std::ffi::CStr; |
| use std::panic::catch_unwind; |
| use std::path::Path; |
| use std::path::PathBuf; |
| use std::time::Duration; |
| |
| use balloon_control::BalloonStats; |
| use balloon_control::BalloonWS; |
| use balloon_control::WSBucket; |
| use libc::c_char; |
| use libc::ssize_t; |
| pub use swap::SwapStatus; |
| use vm_control::client::do_modify_battery; |
| use vm_control::client::do_net_add; |
| use vm_control::client::do_net_remove; |
| use vm_control::client::do_security_key_attach; |
| use vm_control::client::do_usb_attach; |
| use vm_control::client::do_usb_detach; |
| use vm_control::client::do_usb_list; |
| use vm_control::client::handle_request; |
| use vm_control::client::handle_request_with_timeout; |
| use vm_control::client::vms_request; |
| use vm_control::BalloonControlCommand; |
| use vm_control::DiskControlCommand; |
| use vm_control::RegisteredEvent; |
| use vm_control::SwapCommand; |
| use vm_control::UsbControlAttachedDevice; |
| use vm_control::UsbControlResult; |
| use vm_control::VmRequest; |
| use vm_control::VmResponse; |
| use vm_control::USB_CONTROL_MAX_PORTS; |
| |
| pub const VIRTIO_BALLOON_WS_MAX_NUM_BINS: usize = 16; |
| pub const VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS: usize = 15; |
| |
| fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> { |
| if !socket_path.is_null() { |
| // SAFETY: just checked that `socket_path` is not null. |
| let socket_path = unsafe { CStr::from_ptr(socket_path) }; |
| Some(PathBuf::from(socket_path.to_str().ok()?)) |
| } else { |
| None |
| } |
| } |
| |
| /// Stops the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::Exit, socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Suspends the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::SuspendVcpus, socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Resumes the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::ResumeVcpus, socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Creates an RT vCPU for the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_make_rt_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::MakeRT, socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Adjusts the balloon size of the crosvm instance whose control socket is |
| /// listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_vms( |
| socket_path: *const c_char, |
| num_bytes: u64, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| let command = BalloonControlCommand::Adjust { |
| num_bytes, |
| wait_for_success: false, |
| }; |
| vms_request(&VmRequest::BalloonCommand(command), socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// See crosvm_client_balloon_vms. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_vms_wait_with_timeout( |
| socket_path: *const c_char, |
| num_bytes: u64, |
| timeout_ms: u64, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| let command = BalloonControlCommand::Adjust { |
| num_bytes, |
| wait_for_success: true, |
| }; |
| let resp = handle_request_with_timeout( |
| &VmRequest::BalloonCommand(command), |
| socket_path, |
| Some(Duration::from_millis(timeout_ms)), |
| ); |
| if matches!(resp, Ok(VmResponse::Ok)) { |
| return true; |
| } |
| println!("adjust failure: {:?}", resp); |
| } |
| false |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Enable vmm swap for crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::Swap(SwapCommand::Enable), socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Swap out staging memory for crosvm instance whose control socket is listening |
| /// on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::Swap(SwapCommand::SwapOut), socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Arguments structure for crosvm_client_swap_disable_vm2. |
| #[repr(C)] |
| pub struct SwapDisableArgs { |
| /// The path of the control socket to target. |
| socket_path: *const c_char, |
| /// Whether or not the swap file should be cleaned up in the background. |
| slow_file_cleanup: bool, |
| } |
| |
| /// Disable vmm swap according to `args`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_swap_disable_vm(args: *mut SwapDisableArgs) -> bool { |
| catch_unwind(|| { |
| if args.is_null() { |
| return false; |
| } |
| let Some(socket_path) = validate_socket_path((*args).socket_path) else { |
| return false; |
| }; |
| vms_request( |
| &VmRequest::Swap(SwapCommand::Disable { |
| slow_file_cleanup: (*args).slow_file_cleanup, |
| }), |
| socket_path, |
| ) |
| .is_ok() |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Trim staging memory for vmm swap for crosvm instance whose control socket is listening on |
| /// `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_swap_trim(socket_path: *const c_char) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| vms_request(&VmRequest::Swap(SwapCommand::Trim), socket_path).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Returns vmm-swap status of the crosvm instance whose control socket is listening on |
| /// `socket_path`. |
| /// |
| /// The parameters `status` is optional and will only be written to if they are non-null. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_swap_status( |
| socket_path: *const c_char, |
| status: *mut SwapStatus, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| let request = &VmRequest::Swap(SwapCommand::Status); |
| if let Ok(VmResponse::SwapStatus(response)) = handle_request(request, socket_path) { |
| if !status.is_null() { |
| // SAFETY: just checked that `status` is not null. |
| unsafe { |
| *status = response; |
| } |
| } |
| true |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Represents an individual attached USB device. |
| #[repr(C)] |
| pub struct UsbDeviceEntry { |
| /// Internal port index used for identifying this individual device. |
| port: u8, |
| /// USB vendor ID |
| vendor_id: u16, |
| /// USB product ID |
| product_id: u16, |
| } |
| |
| impl From<&UsbControlAttachedDevice> for UsbDeviceEntry { |
| fn from(other: &UsbControlAttachedDevice) -> Self { |
| Self { |
| port: other.port, |
| vendor_id: other.vendor_id, |
| product_id: other.product_id, |
| } |
| } |
| } |
| |
| /// Simply returns the maximum possible number of USB devices |
| #[no_mangle] |
| pub extern "C" fn crosvm_client_max_usb_devices() -> usize { |
| USB_CONTROL_MAX_PORTS |
| } |
| |
| /// Returns all USB devices passed through the crosvm instance whose control socket is listening on |
| /// `socket_path`. |
| /// |
| /// The function returns the amount of entries written. |
| /// # Arguments |
| /// |
| /// * `socket_path` - Path to the crosvm control socket |
| /// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached |
| /// devices will be written to |
| /// * `entries_length` - Amount of entries in the array specified by `entries` |
| /// |
| /// Use the value returned by [`crosvm_client_max_usb_devices()`] to determine the size of the input |
| /// array to this function. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_usb_list( |
| socket_path: *const c_char, |
| entries: *mut UsbDeviceEntry, |
| entries_length: ssize_t, |
| ) -> ssize_t { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if entries.is_null() { |
| return -1; |
| } |
| if let Ok(UsbControlResult::Devices(res)) = do_usb_list(socket_path) { |
| let mut i = 0; |
| for entry in res.iter().filter(|x| x.valid()) { |
| if i >= entries_length { |
| break; |
| } |
| // SAFETY: checked that `entries` is not null. |
| unsafe { |
| *entries.offset(i) = entry.into(); |
| i += 1; |
| } |
| } |
| i |
| } else { |
| -1 |
| } |
| } else { |
| -1 |
| } |
| }) |
| .unwrap_or(-1) |
| } |
| |
| /// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns the amount of entries written. |
| /// # Arguments |
| /// |
| /// * `socket_path` - Path to the crosvm control socket |
| /// * `bus` - USB device bus ID (unused) |
| /// * `addr` - USB device address (unused) |
| /// * `vid` - USB device vendor ID (unused) |
| /// * `pid` - USB device product ID (unused) |
| /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`). |
| /// * `out_port` - (optional) internal port will be written here if provided. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage. |
| /// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should |
| /// ensure no null pointers are passed into the function. |
| /// |
| /// The safety requirements for `socket_path` and `dev_path` are the same as the ones from |
| /// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte |
| /// region. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_usb_attach( |
| socket_path: *const c_char, |
| _bus: u8, |
| _addr: u8, |
| _vid: u16, |
| _pid: u16, |
| dev_path: *const c_char, |
| out_port: *mut u8, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if dev_path.is_null() { |
| return false; |
| } |
| // SAFETY: just checked that `dev_path` is not null. |
| let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or("")); |
| |
| if let Ok(UsbControlResult::Ok { port }) = do_usb_attach(socket_path, dev_path) { |
| if !out_port.is_null() { |
| // SAFETY: trivially safe |
| unsafe { *out_port = port }; |
| } |
| true |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Attaches a u2f security key to crosvm instance whose control socket is listening on |
| /// `socket_path`. |
| /// |
| /// The function returns the amount of entries written. |
| /// # Arguments |
| /// |
| /// * `socket_path` - Path to the crosvm control socket |
| /// * `hidraw_path` - Path to the hidraw device of the security key (like `/dev/hidraw0`) |
| /// * `out_port` - (optional) internal port will be written here if provided. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage. |
| /// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should |
| /// ensure no null pointers are passed into the function. |
| /// |
| /// The safety requirements for `socket_path` and `hidraw_path` are the same as the ones from |
| /// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte |
| /// region. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_security_key_attach( |
| socket_path: *const c_char, |
| hidraw_path: *const c_char, |
| out_port: *mut u8, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if hidraw_path.is_null() { |
| return false; |
| } |
| let hidraw_path = Path::new( |
| // SAFETY: just checked that `hidraw_path` is not null. |
| unsafe { CStr::from_ptr(hidraw_path) } |
| .to_str() |
| .unwrap_or(""), |
| ); |
| |
| if let Ok(UsbControlResult::Ok { port }) = |
| do_security_key_attach(socket_path, hidraw_path) |
| { |
| if !out_port.is_null() { |
| // SAFETY: trivially safe |
| unsafe { *out_port = port }; |
| } |
| true |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`. |
| /// `port` determines device to be detached. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| do_usb_detach(socket_path, port).is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Attaches a net tap device to the crosvm instance with control socket at `socket_path`. |
| /// |
| /// # Arguments |
| /// |
| /// * `socket_path` - Path to the crosvm control socket |
| /// * `tap_name` - Name of the tap device |
| /// * `out_bus_num` - guest bus number will be written here |
| /// |
| /// The function returns true on success, false on failure. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - socket_path and tap_name are assumed to point to a |
| /// null-terminated CStr. Function checks that the pointers are not null, but caller need to check |
| /// the validity of the pointer. out_bus_num is assumed to point to a u8 integer. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_net_tap_attach( |
| socket_path: *const c_char, |
| tap_name: *const c_char, |
| out_bus_num: *mut u8, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if tap_name.is_null() || out_bus_num.is_null() { |
| return false; |
| } |
| // SAFETY: just checked that `tap_name` is not null. Function caller guarantees it |
| // points to a valid CStr. |
| let tap_name = unsafe { CStr::from_ptr(tap_name) }.to_str().unwrap_or(""); |
| |
| match do_net_add(tap_name, socket_path) { |
| Ok(bus_num) => { |
| // SAFETY: checked out_bus_num is not null. Function caller guarantees |
| // validity of pointer. |
| unsafe { *out_bus_num = bus_num }; |
| true |
| } |
| Err(_e) => false, |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Detaches a hotplugged tap device from the crosvm instance with control socket at `socket_path`. |
| /// |
| /// # Arguments |
| /// |
| /// * `socket_path` - Path to the crosvm control socket |
| /// * `bus_num` - Bus number of the tap device to be removed. |
| /// |
| /// The function returns true on success, and false on failure. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - socket_path is assumed to point to a |
| /// null-terminated Cstr. Function checks that the pointers are not null, but caller need to check |
| /// the validity of the pointer. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_net_tap_detach( |
| socket_path: *const c_char, |
| bus_num: u8, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| match do_net_remove(bus_num, socket_path) { |
| Ok(()) => true, |
| Err(_e) => false, |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Modifies the battery status of crosvm instance whose control socket is listening on |
| /// `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_modify_battery( |
| socket_path: *const c_char, |
| battery_type: *const c_char, |
| property: *const c_char, |
| target: *const c_char, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if battery_type.is_null() || property.is_null() || target.is_null() { |
| return false; |
| } |
| // SAFETY: trivially safe |
| let battery_type = unsafe { CStr::from_ptr(battery_type) }; |
| // SAFETY: trivially safe |
| let property = unsafe { CStr::from_ptr(property) }; |
| // SAFETY: trivially safe |
| let target = unsafe { CStr::from_ptr(target) }; |
| |
| do_modify_battery( |
| socket_path, |
| battery_type.to_str().unwrap(), |
| property.to_str().unwrap(), |
| target.to_str().unwrap(), |
| ) |
| .is_ok() |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_resize_disk( |
| socket_path: *const c_char, |
| disk_index: u64, |
| new_size: u64, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if let Ok(disk_index) = usize::try_from(disk_index) { |
| let request = VmRequest::DiskCommand { |
| disk_index, |
| command: DiskControlCommand::Resize { new_size }, |
| }; |
| vms_request(&request, socket_path).is_ok() |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Similar to internally used `BalloonStats` but using `i64` instead of |
| /// `Option<u64>`. `None` (or values bigger than `i64::max`) will be encoded as -1. |
| #[repr(C)] |
| pub struct BalloonStatsFfi { |
| swap_in: i64, |
| swap_out: i64, |
| major_faults: i64, |
| minor_faults: i64, |
| free_memory: i64, |
| total_memory: i64, |
| available_memory: i64, |
| disk_caches: i64, |
| hugetlb_allocations: i64, |
| hugetlb_failures: i64, |
| shared_memory: i64, |
| unevictable_memory: i64, |
| } |
| |
| impl From<&BalloonStats> for BalloonStatsFfi { |
| fn from(other: &BalloonStats) -> Self { |
| let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) }; |
| Self { |
| swap_in: convert(other.swap_in), |
| swap_out: convert(other.swap_out), |
| major_faults: convert(other.major_faults), |
| minor_faults: convert(other.minor_faults), |
| free_memory: convert(other.free_memory), |
| total_memory: convert(other.total_memory), |
| available_memory: convert(other.available_memory), |
| disk_caches: convert(other.disk_caches), |
| hugetlb_allocations: convert(other.hugetlb_allocations), |
| hugetlb_failures: convert(other.hugetlb_failures), |
| shared_memory: convert(other.shared_memory), |
| unevictable_memory: convert(other.unevictable_memory), |
| } |
| } |
| } |
| |
| /// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`. |
| /// |
| /// The parameters `stats` and `actual` are optional and will only be written to if they are |
| /// non-null. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Note |
| /// |
| /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_stats( |
| socket_path: *const c_char, |
| stats: *mut BalloonStatsFfi, |
| actual: *mut u64, |
| ) -> bool { |
| crosvm_client_balloon_stats_impl(socket_path, None, stats, actual) |
| } |
| |
| /// See crosvm_client_balloon_stats. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_stats_with_timeout( |
| socket_path: *const c_char, |
| timeout_ms: u64, |
| stats: *mut BalloonStatsFfi, |
| actual: *mut u64, |
| ) -> bool { |
| crosvm_client_balloon_stats_impl( |
| socket_path, |
| Some(Duration::from_millis(timeout_ms)), |
| stats, |
| actual, |
| ) |
| } |
| |
| fn crosvm_client_balloon_stats_impl( |
| socket_path: *const c_char, |
| timeout_ms: Option<Duration>, |
| stats: *mut BalloonStatsFfi, |
| actual: *mut u64, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {}); |
| let resp = handle_request_with_timeout(request, socket_path, timeout_ms); |
| if let Ok(VmResponse::BalloonStats { |
| stats: ref balloon_stats, |
| balloon_actual, |
| }) = resp |
| { |
| if !stats.is_null() { |
| // SAFETY: just checked that `stats` is not null. |
| unsafe { |
| *stats = balloon_stats.into(); |
| } |
| } |
| |
| if !actual.is_null() { |
| // SAFETY: just checked that `actual` is not null. |
| unsafe { |
| *actual = balloon_actual; |
| } |
| } |
| true |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Externally exposed variant of BalloonWS/WSBucket, used for FFI. |
| #[derive(Clone, Copy, Debug)] |
| #[repr(C)] |
| pub struct WorkingSetBucketFfi { |
| age: u64, |
| bytes: [u64; 2], |
| } |
| |
| impl WorkingSetBucketFfi { |
| fn new() -> Self { |
| Self { |
| age: 0, |
| bytes: [0, 0], |
| } |
| } |
| } |
| |
| impl From<WSBucket> for WorkingSetBucketFfi { |
| fn from(other: WSBucket) -> Self { |
| Self { |
| age: other.age, |
| bytes: other.bytes, |
| } |
| } |
| } |
| |
| #[repr(C)] |
| #[derive(Debug)] |
| pub struct BalloonWSFfi { |
| ws: [WorkingSetBucketFfi; VIRTIO_BALLOON_WS_MAX_NUM_BINS], |
| num_bins: u8, |
| _reserved: [u8; 7], |
| } |
| |
| impl TryFrom<&BalloonWS> for BalloonWSFfi { |
| type Error = &'static str; |
| |
| fn try_from(value: &BalloonWS) -> Result<Self, Self::Error> { |
| if value.ws.len() > VIRTIO_BALLOON_WS_MAX_NUM_BINS { |
| return Err("too many WS buckets in source object."); |
| } |
| |
| let mut ffi = Self { |
| ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS], |
| num_bins: value.ws.len() as u8, |
| ..Default::default() |
| }; |
| for (ffi_ws, other_ws) in ffi.ws.iter_mut().zip(value.ws.iter()) { |
| *ffi_ws = (*other_ws).into(); |
| } |
| Ok(ffi) |
| } |
| } |
| |
| impl BalloonWSFfi { |
| pub fn new() -> Self { |
| Self { |
| ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS], |
| num_bins: 0, |
| _reserved: [0; 7], |
| } |
| } |
| } |
| |
| impl Default for BalloonWSFfi { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| #[repr(C)] |
| pub struct BalloonWSRConfigFfi { |
| intervals: [u64; VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS], |
| num_intervals: u8, |
| _reserved: [u8; 7], |
| refresh_threshold: u64, |
| report_threshold: u64, |
| } |
| |
| /// Returns balloon working set of the crosvm instance whose control socket is listening on |
| /// socket_path. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_working_set( |
| socket_path: *const c_char, |
| ws: *mut BalloonWSFfi, |
| actual: *mut u64, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| let request = &VmRequest::BalloonCommand(BalloonControlCommand::WorkingSet); |
| if let Ok(VmResponse::BalloonWS { |
| ws: ref balloon_ws, |
| balloon_actual, |
| }) = handle_request(request, socket_path) |
| { |
| if !ws.is_null() { |
| // SAFETY: just checked that `ws` is not null. |
| unsafe { |
| *ws = match balloon_ws.try_into() { |
| Ok(result) => result, |
| Err(_) => return false, |
| }; |
| } |
| } |
| |
| if !actual.is_null() { |
| // SAFETY: just checked that `actual` is not null. |
| unsafe { |
| *actual = balloon_actual; |
| } |
| } |
| true |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Publically exposed version of RegisteredEvent enum, implemented as an |
| /// integral newtype for FFI safety. |
| #[repr(C)] |
| #[derive(Copy, Clone, PartialEq, Eq)] |
| pub struct RegisteredEventFfi(u32); |
| |
| pub const REGISTERED_EVENT_VIRTIO_BALLOON_WS_REPORT: RegisteredEventFfi = RegisteredEventFfi(0); |
| pub const REGISTERED_EVENT_VIRTIO_BALLOON_RESIZE: RegisteredEventFfi = RegisteredEventFfi(1); |
| pub const REGISTERED_EVENT_VIRTIO_BALLOON_OOM_DEFLATION: RegisteredEventFfi = RegisteredEventFfi(2); |
| |
| impl TryFrom<RegisteredEventFfi> for RegisteredEvent { |
| type Error = &'static str; |
| |
| fn try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error> { |
| match value.0 { |
| 0 => Ok(RegisteredEvent::VirtioBalloonWsReport), |
| 1 => Ok(RegisteredEvent::VirtioBalloonResize), |
| 2 => Ok(RegisteredEvent::VirtioBalloonOOMDeflation), |
| _ => Err("RegisteredEventFFi outside of known RegisteredEvent enum range"), |
| } |
| } |
| } |
| |
| /// Registers the connected process as a listener for `event`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_register_events_listener( |
| socket_path: *const c_char, |
| listening_socket_path: *const c_char, |
| event: RegisteredEventFfi, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) { |
| if let Ok(event) = event.try_into() { |
| let request = VmRequest::RegisterListener { |
| event, |
| socket_addr: listening_socket_path.to_str().unwrap().to_string(), |
| }; |
| vms_request(&request, socket_path).is_ok() |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Unegisters the connected process as a listener for `event`. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_unregister_events_listener( |
| socket_path: *const c_char, |
| listening_socket_path: *const c_char, |
| event: RegisteredEventFfi, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) { |
| if let Ok(event) = event.try_into() { |
| let request = VmRequest::UnregisterListener { |
| event, |
| socket_addr: listening_socket_path.to_str().unwrap().to_string(), |
| }; |
| vms_request(&request, socket_path).is_ok() |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Unegisters the connected process as a listener for all events. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_unregister_listener( |
| socket_path: *const c_char, |
| listening_socket_path: *const c_char, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) { |
| let request = VmRequest::Unregister { |
| socket_addr: listening_socket_path.to_str().unwrap().to_string(), |
| }; |
| vms_request(&request, socket_path).is_ok() |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |
| |
| /// Set Working Set Reporting config in guest. |
| /// |
| /// The function returns true on success or false if an error occurred. |
| /// |
| /// # Safety |
| /// |
| /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of |
| /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no |
| /// null pointers are passed. |
| #[no_mangle] |
| pub unsafe extern "C" fn crosvm_client_balloon_wsr_config( |
| socket_path: *const c_char, |
| config: *const BalloonWSRConfigFfi, |
| ) -> bool { |
| catch_unwind(|| { |
| if let Some(socket_path) = validate_socket_path(socket_path) { |
| if !config.is_null() { |
| // SAFETY: just checked that `config` is not null. |
| unsafe { |
| if (*config).num_intervals > VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS as u8 { |
| return false; |
| } |
| let mut actual_bins = vec![]; |
| for idx in 0..(*config).num_intervals { |
| actual_bins.push((*config).intervals[idx as usize]); |
| } |
| let refresh_threshold = match u32::try_from((*config).refresh_threshold) { |
| Ok(r_t) => r_t, |
| Err(_) => return false, |
| }; |
| let report_threshold = match u32::try_from((*config).report_threshold) { |
| Ok(r_p) => r_p, |
| Err(_) => return false, |
| }; |
| let request = |
| VmRequest::BalloonCommand(BalloonControlCommand::WorkingSetConfig { |
| bins: actual_bins |
| .iter() |
| .map(|&b| u32::try_from(b).unwrap()) |
| .collect(), |
| refresh_threshold, |
| report_threshold, |
| }); |
| vms_request(&request, socket_path).is_ok() |
| } |
| } else { |
| false |
| } |
| } else { |
| false |
| } |
| }) |
| .unwrap_or(false) |
| } |