// Copyright 2021 The Chromium OS Authors. All rights reserved. // 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. // Usually you would need to invoke crosvm with subcommands and you'd get the result on // stdout. use std::convert::{TryFrom, TryInto}; use std::ffi::CStr; use std::panic::catch_unwind; use std::path::{Path, PathBuf}; use libc::{c_char, ssize_t}; use vm_control::{ client::*, BalloonControlCommand, BalloonStats, DiskControlCommand, UsbControlAttachedDevice, UsbControlResult, VmRequest, VmResponse, }; fn validate_socket_path(socket_path: *const c_char) -> Option { if !socket_path.is_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 occured. #[no_mangle] pub 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 occured. #[no_mangle] pub 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::Suspend, &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 occured. #[no_mangle] pub 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::Resume, &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 occured. #[no_mangle] pub 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 }; vms_request(&VmRequest::BalloonCommand(command), &socket_path).is_ok() } 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, } } } /// 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` /// /// Crosvm supports passing through up to 255 devices, so pasing an array with 255 entries will /// guarantee to return all entries. #[no_mangle] pub 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 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; } 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 /// * `addr` - USB device address /// * `vid` - USB device vendor ID /// * `pid` - USB device product ID /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb//`). /// * `out_port` - (optional) internal port will be written here if provided. /// /// The function returns true on success or false if an error occured. #[no_mangle] pub 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; } 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, bus, addr, vid, pid, dev_path) { if !out_port.is_null() { 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 occured. #[no_mangle] pub 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) } /// 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 occured. #[no_mangle] pub 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; } let battery_type = unsafe { CStr::from_ptr(battery_type) }; let property = unsafe { CStr::from_ptr(property) }; 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 occured. #[no_mangle] pub 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. `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, } impl From<&BalloonStats> for BalloonStatsFfi { fn from(other: &BalloonStats) -> Self { let convert = |x: Option| -> i64 { x.map(|y| y.try_into().ok()).flatten().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), } } } /// 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 occured. /// /// # Note /// /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`. #[no_mangle] pub extern "C" fn crosvm_client_balloon_stats( socket_path: *const c_char, 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 {}); if let Ok(VmResponse::BalloonStats { stats: ref balloon_stats, balloon_actual, }) = handle_request(request, &socket_path) { if !stats.is_null() { unsafe { *stats = balloon_stats.into(); } } if !actual.is_null() { unsafe { *actual = balloon_actual; } } true } else { false } } else { false } }) .unwrap_or(false) }