Add FFI library providing control socket access
This allows other languages to communicate directly with the control
socket without having to invoke `crosvm`
BUG=None
TEST=Ran ./run_tests
Change-Id: Icbf5905c41643b080bae3613b73a032467db1c4c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2772798
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Kevin Hamacher <hamacher@google.com>
Commit-Queue: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
diff --git a/Cargo.lock b/Cargo.lock
index 4d63985..725b837 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -587,6 +587,7 @@
"libc",
"log",
"protobuf",
+ "sys_util",
"thiserror",
"zeroize",
]
@@ -603,6 +604,15 @@
]
[[package]]
+name = "libcrosvm_control"
+version = "0.1.0"
+dependencies = [
+ "base",
+ "libc",
+ "vm_control",
+]
+
+[[package]]
name = "libdbus-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index d068019..3b2f0cf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@
"fuzz",
"qcow_utils",
"integration_tests",
+ "libcrosvm_control",
]
exclude = [
"assertions",
diff --git a/libcrosvm_control/Cargo.toml b/libcrosvm_control/Cargo.toml
new file mode 100644
index 0000000..2ed68b8
--- /dev/null
+++ b/libcrosvm_control/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "libcrosvm_control"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+base = { path = "../base" }
+vm_control = { path = "../vm_control" }
+libc = "0.2.65"
diff --git a/libcrosvm_control/src/lib.rs b/libcrosvm_control/src/lib.rs
new file mode 100644
index 0000000..973d64e
--- /dev/null
+++ b/libcrosvm_control/src/lib.rs
@@ -0,0 +1,358 @@
+// 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<PathBuf> {
+ 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/<bus>/<addr>`).
+/// * `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<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,
+}
+
+impl From<&BalloonStats> for BalloonStatsFfi {
+ fn from(other: &BalloonStats) -> Self {
+ let convert =
+ |x: Option<u64>| -> 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)
+}
diff --git a/run_tests b/run_tests
index 89b71ca..aa705b5 100755
--- a/run_tests
+++ b/run_tests
@@ -45,6 +45,7 @@
"kernel_loader": [Requirements.PRIVILEGED],
"kvm_sys": [Requirements.PRIVILEGED],
"kvm": [Requirements.PRIVILEGED, Requirements.X86_64],
+ "libcrosvm_control": [],
"linux_input_sys": [],
"net_sys": [],
"net_util": [Requirements.PRIVILEGED],
diff --git a/src/main.rs b/src/main.rs
index 3d166e8..ab3786e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,10 +8,8 @@
use std::collections::BTreeMap;
use std::default::Default;
-use std::fmt;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
-use std::num::ParseIntError;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::string::String;
@@ -22,10 +20,7 @@
set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType,
VcpuAffinity,
};
-use base::{
- debug, error, getpid, info, kill_process_group, net::UnixSeqpacket, reap_child, syslog,
- validate_raw_descriptor, warn, FromRawDescriptor, RawDescriptor, Tube,
-};
+use base::{debug, error, getpid, info, kill_process_group, reap_child, syslog, warn};
#[cfg(feature = "direct")]
use crosvm::DirectIoOption;
use crosvm::{
@@ -40,8 +35,11 @@
use devices::{Ac97Backend, Ac97Parameters};
use disk::QcowFile;
use vm_control::{
- BalloonControlCommand, BatControlCommand, BatControlResult, BatteryType, DiskControlCommand,
- UsbControlCommand, UsbControlResult, VmRequest, VmResponse, USB_CONTROL_MAX_PORTS,
+ client::{
+ do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
+ ModifyUsbError, ModifyUsbResult,
+ },
+ BalloonControlCommand, BatteryType, DiskControlCommand, UsbControlResult, VmRequest,
};
fn executable_is_plugin(executable: &Option<Executable>) -> bool {
@@ -1993,76 +1991,37 @@
}
}
-fn handle_request(
- request: &VmRequest,
- args: std::env::Args,
-) -> std::result::Result<VmResponse, ()> {
- let mut return_result = Err(());
- for socket_path in args {
- match UnixSeqpacket::connect(&socket_path) {
- Ok(s) => {
- let socket = Tube::new(s);
- if let Err(e) = socket.send(request) {
- error!(
- "failed to send request to socket at '{}': {}",
- socket_path, e
- );
- return_result = Err(());
- continue;
- }
- match socket.recv() {
- Ok(response) => return_result = Ok(response),
- Err(e) => {
- error!(
- "failed to send request to socket at2 '{}': {}",
- socket_path, e
- );
- return_result = Err(());
- continue;
- }
- }
- }
- Err(e) => {
- error!("failed to connect to socket at '{}': {}", socket_path, e);
- return_result = Err(());
- }
- }
- }
-
- return_result
-}
-
-fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
- let response = handle_request(request, args)?;
- info!("request response was {}", response);
- Ok(())
-}
-
-fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
+fn stop_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() == 0 {
print_help("crosvm stop", "VM_SOCKET...", &[]);
println!("Stops the crosvm instance listening on each `VM_SOCKET` given.");
return Err(());
}
- vms_request(&VmRequest::Exit, args)
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::Exit, socket_path)
}
-fn suspend_vms(args: std::env::Args) -> std::result::Result<(), ()> {
+fn suspend_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() == 0 {
print_help("crosvm suspend", "VM_SOCKET...", &[]);
println!("Suspends the crosvm instance listening on each `VM_SOCKET` given.");
return Err(());
}
- vms_request(&VmRequest::Suspend, args)
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::Suspend, socket_path)
}
-fn resume_vms(args: std::env::Args) -> std::result::Result<(), ()> {
+fn resume_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() == 0 {
print_help("crosvm resume", "VM_SOCKET...", &[]);
println!("Resumes the crosvm instance listening on each `VM_SOCKET` given.");
return Err(());
}
- vms_request(&VmRequest::Resume, args)
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::Resume, socket_path)
}
fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
@@ -2080,10 +2039,12 @@
};
let command = BalloonControlCommand::Adjust { num_bytes };
- vms_request(&VmRequest::BalloonCommand(command), args)
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::BalloonCommand(command), socket_path)
}
-fn balloon_stats(args: std::env::Args) -> std::result::Result<(), ()> {
+fn balloon_stats(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() != 1 {
print_help("crosvm balloon_stats", "VM_SOCKET", &[]);
println!("Prints virtio balloon statistics for a `VM_SOCKET`.");
@@ -2091,7 +2052,9 @@
}
let command = BalloonControlCommand::Stats {};
let request = &VmRequest::BalloonCommand(command);
- let response = handle_request(request, args)?;
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ let response = handle_request(request, socket_path)?;
println!("{}", response);
Ok(())
}
@@ -2214,47 +2177,11 @@
}
};
- vms_request(&request, args)
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&request, socket_path)
}
-enum ModifyUsbError {
- ArgMissing(&'static str),
- ArgParse(&'static str, String),
- ArgParseInt(&'static str, String, ParseIntError),
- FailedDescriptorValidate(base::Error),
- PathDoesNotExist(PathBuf),
- SocketFailed,
- UnexpectedResponse(VmResponse),
- UnknownCommand(String),
- UsbControl(UsbControlResult),
-}
-
-impl fmt::Display for ModifyUsbError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::ModifyUsbError::*;
-
- match self {
- ArgMissing(a) => write!(f, "argument missing: {}", a),
- ArgParse(name, value) => {
- write!(f, "failed to parse argument {} value `{}`", name, value)
- }
- ArgParseInt(name, value, e) => write!(
- f,
- "failed to parse integer argument {} value `{}`: {}",
- name, value, e
- ),
- FailedDescriptorValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
- PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
- SocketFailed => write!(f, "socket failed"),
- UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
- UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
- UsbControl(e) => write!(f, "{}", e),
- }
- }
-}
-
-type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
-
fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
debug!("parse_bus_id_addr: {}", v);
let mut ids = v.split(':');
@@ -2279,27 +2206,6 @@
}
}
-fn raw_descriptor_from_path(path: &Path) -> ModifyUsbResult<RawDescriptor> {
- if !path.exists() {
- return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
- }
- let raw_descriptor = path
- .file_name()
- .and_then(|fd_osstr| fd_osstr.to_str())
- .map_or(
- Err(ModifyUsbError::ArgParse(
- "USB_DEVICE_PATH",
- path.to_string_lossy().into_owned(),
- )),
- |fd_str| {
- fd_str.parse::<libc::c_int>().map_err(|e| {
- ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
- })
- },
- )?;
- validate_raw_descriptor(raw_descriptor).map_err(ModifyUsbError::FailedDescriptorValidate)
-}
-
fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
let val = args
.next()
@@ -2309,30 +2215,13 @@
args.next()
.ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
);
- let usb_file = if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
- // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
- // Safe because we will validate |raw_fd|.
- unsafe { File::from_raw_descriptor(raw_descriptor_from_path(&dev_path)?) }
- } else {
- OpenOptions::new()
- .read(true)
- .write(true)
- .open(&dev_path)
- .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?
- };
- let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
- bus,
- addr,
- vid,
- pid,
- file: usb_file,
- });
- let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
- match response {
- VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
- r => Err(ModifyUsbError::UnexpectedResponse(r)),
- }
+ let socket_path = args
+ .next()
+ .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
+ let socket_path = Path::new(&socket_path);
+
+ do_usb_attach(&socket_path, bus, addr, vid, pid, &dev_path)
}
fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
@@ -2342,25 +2231,19 @@
p.parse::<u8>()
.map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
})?;
- let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
- let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
- match response {
- VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
- r => Err(ModifyUsbError::UnexpectedResponse(r)),
- }
+ let socket_path = args
+ .next()
+ .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
+ let socket_path = Path::new(&socket_path);
+ do_usb_detach(&socket_path, port)
}
-fn usb_list(args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
- let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
- for (index, port) in ports.iter_mut().enumerate() {
- *port = index as u8
- }
- let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
- let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
- match response {
- VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
- r => Err(ModifyUsbError::UnexpectedResponse(r)),
- }
+fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
+ let socket_path = args
+ .next()
+ .ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
+ let socket_path = Path::new(&socket_path);
+ do_usb_list(&socket_path)
}
fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
@@ -2371,7 +2254,7 @@
}
// This unwrap will not panic because of the above length check.
- let command = args.next().unwrap();
+ let command = &args.next().unwrap();
let result = match command.as_ref() {
"attach" => usb_attach(args),
"detach" => usb_detach(args),
@@ -2414,20 +2297,6 @@
Ok(())
}
-enum ModifyBatError {
- BatControlErr(BatControlResult),
-}
-
-impl fmt::Display for ModifyBatError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::ModifyBatError::*;
-
- match self {
- BatControlErr(e) => write!(f, "{}", e),
- }
- }
-}
-
fn modify_battery(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() < 4 {
print_help("crosvm battery BATTERY_TYPE ",
@@ -2440,27 +2309,10 @@
let property = args.next().unwrap();
let target = args.next().unwrap();
- let response = match battery_type.parse::<BatteryType>() {
- Ok(type_) => match BatControlCommand::new(property, target) {
- Ok(cmd) => {
- let request = VmRequest::BatCommand(type_, cmd);
- Ok(handle_request(&request, args)?)
- }
- Err(e) => Err(ModifyBatError::BatControlErr(e)),
- },
- Err(e) => Err(ModifyBatError::BatControlErr(e)),
- };
+ let socket_path = args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
- match response {
- Ok(response) => {
- println!("{}", response);
- Ok(())
- }
- Err(e) => {
- println!("error {}", e);
- Err(())
- }
- }
+ do_modify_battery(&socket_path, &*battery_type, &*property, &*target)
}
fn crosvm_main() -> std::result::Result<(), ()> {
diff --git a/vm_control/src/client.rs b/vm_control/src/client.rs
new file mode 100644
index 0000000..3fff3e3
--- /dev/null
+++ b/vm_control/src/client.rs
@@ -0,0 +1,211 @@
+// 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.
+use crate::*;
+use base::{info, net::UnixSeqpacket, validate_raw_descriptor, RawDescriptor, Tube};
+
+use std::fs::OpenOptions;
+use std::num::ParseIntError;
+use std::path::{Path, PathBuf};
+
+enum ModifyBatError {
+ BatControlErr(BatControlResult),
+}
+
+impl fmt::Display for ModifyBatError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ModifyBatError::*;
+
+ match self {
+ BatControlErr(e) => write!(f, "{}", e),
+ }
+ }
+}
+
+pub enum ModifyUsbError {
+ ArgMissing(&'static str),
+ ArgParse(&'static str, String),
+ ArgParseInt(&'static str, String, ParseIntError),
+ FailedDescriptorValidate(base::Error),
+ PathDoesNotExist(PathBuf),
+ SocketFailed,
+ UnexpectedResponse(VmResponse),
+ UnknownCommand(String),
+ UsbControl(UsbControlResult),
+}
+
+impl std::fmt::Display for ModifyUsbError {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ use self::ModifyUsbError::*;
+
+ match self {
+ ArgMissing(a) => write!(f, "argument missing: {}", a),
+ ArgParse(name, value) => {
+ write!(f, "failed to parse argument {} value `{}`", name, value)
+ }
+ ArgParseInt(name, value, e) => write!(
+ f,
+ "failed to parse integer argument {} value `{}`: {}",
+ name, value, e
+ ),
+ FailedDescriptorValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
+ PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
+ SocketFailed => write!(f, "socket failed"),
+ UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
+ UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
+ UsbControl(e) => write!(f, "{}", e),
+ }
+ }
+}
+
+pub type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
+
+fn raw_descriptor_from_path(path: &Path) -> ModifyUsbResult<RawDescriptor> {
+ if !path.exists() {
+ return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
+ }
+ let raw_descriptor = path
+ .file_name()
+ .and_then(|fd_osstr| fd_osstr.to_str())
+ .map_or(
+ Err(ModifyUsbError::ArgParse(
+ "USB_DEVICE_PATH",
+ path.to_string_lossy().into_owned(),
+ )),
+ |fd_str| {
+ fd_str.parse::<libc::c_int>().map_err(|e| {
+ ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
+ })
+ },
+ )?;
+ validate_raw_descriptor(raw_descriptor).map_err(ModifyUsbError::FailedDescriptorValidate)
+}
+
+pub type VmsRequestResult = std::result::Result<(), ()>;
+
+pub fn vms_request(request: &VmRequest, socket_path: &Path) -> VmsRequestResult {
+ let response = handle_request(request, socket_path)?;
+ info!("request response was {}", response);
+ Ok(())
+}
+
+pub fn do_usb_attach(
+ socket_path: &Path,
+ bus: u8,
+ addr: u8,
+ vid: u16,
+ pid: u16,
+ dev_path: &Path,
+) -> ModifyUsbResult<UsbControlResult> {
+ let usb_file: File = if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
+ // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
+ // Safe because we will validate |raw_fd|.
+ unsafe { File::from_raw_descriptor(raw_descriptor_from_path(&dev_path)?) }
+ } else {
+ OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(&dev_path)
+ .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?
+ };
+
+ let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
+ bus,
+ addr,
+ vid,
+ pid,
+ file: usb_file,
+ });
+ let response =
+ handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
+ match response {
+ VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+ r => Err(ModifyUsbError::UnexpectedResponse(r)),
+ }
+}
+
+pub fn do_usb_detach(socket_path: &Path, port: u8) -> ModifyUsbResult<UsbControlResult> {
+ let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
+ let response =
+ handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
+ match response {
+ VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+ r => Err(ModifyUsbError::UnexpectedResponse(r)),
+ }
+}
+
+pub fn do_usb_list(socket_path: &Path) -> ModifyUsbResult<UsbControlResult> {
+ let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
+ for (index, port) in ports.iter_mut().enumerate() {
+ *port = index as u8
+ }
+ let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
+ let response =
+ handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
+ match response {
+ VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+ r => Err(ModifyUsbError::UnexpectedResponse(r)),
+ }
+}
+
+pub type DoModifyBatteryResult = std::result::Result<(), ()>;
+
+pub fn do_modify_battery(
+ socket_path: &Path,
+ battery_type: &str,
+ property: &str,
+ target: &str,
+) -> DoModifyBatteryResult {
+ let response = match battery_type.parse::<BatteryType>() {
+ Ok(type_) => match BatControlCommand::new(property.to_string(), target.to_string()) {
+ Ok(cmd) => {
+ let request = VmRequest::BatCommand(type_, cmd);
+ Ok(handle_request(&request, socket_path)?)
+ }
+ Err(e) => Err(ModifyBatError::BatControlErr(e)),
+ },
+ Err(e) => Err(ModifyBatError::BatControlErr(e)),
+ };
+
+ match response {
+ Ok(response) => {
+ println!("{}", response);
+ Ok(())
+ }
+ Err(e) => {
+ println!("error {}", e);
+ Err(())
+ }
+ }
+}
+
+pub type HandleRequestResult = std::result::Result<VmResponse, ()>;
+
+pub fn handle_request(request: &VmRequest, socket_path: &Path) -> HandleRequestResult {
+ match UnixSeqpacket::connect(&socket_path) {
+ Ok(s) => {
+ let socket = Tube::new(s);
+ if let Err(e) = socket.send(request) {
+ error!(
+ "failed to send request to socket at '{:?}': {}",
+ socket_path, e
+ );
+ return Err(());
+ }
+ match socket.recv() {
+ Ok(response) => Ok(response),
+ Err(e) => {
+ error!(
+ "failed to send request to socket at '{:?}': {}",
+ socket_path, e
+ );
+ Err(())
+ }
+ }
+ }
+ Err(e) => {
+ error!("failed to connect to socket at '{:?}': {}", socket_path, e);
+ Err(())
+ }
+ }
+}
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index 7853b3d..88dabf7 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -13,6 +13,8 @@
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub mod gdb;
+pub mod client;
+
use std::fmt::{self, Display};
use std::fs::File;
use std::os::raw::c_int;
@@ -215,7 +217,7 @@
}
impl UsbControlAttachedDevice {
- fn valid(self) -> bool {
+ pub fn valid(self) -> bool {
self.port != 0
}
}