vm_control/
client.rs

1// Copyright 2021 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fs::OpenOptions;
6use std::path::Path;
7use std::path::PathBuf;
8
9#[cfg(feature = "pci-hotplug")]
10use anyhow::anyhow;
11use anyhow::Result as AnyHowResult;
12use base::open_file_or_duplicate;
13use remain::sorted;
14use thiserror::Error;
15
16#[cfg(feature = "gpu")]
17pub use crate::gpu::do_gpu_display_add;
18#[cfg(feature = "gpu")]
19pub use crate::gpu::do_gpu_display_list;
20#[cfg(feature = "gpu")]
21pub use crate::gpu::do_gpu_display_remove;
22#[cfg(feature = "gpu")]
23pub use crate::gpu::do_gpu_set_display_mouse_mode;
24#[cfg(feature = "gpu")]
25pub use crate::gpu::ModifyGpuResult;
26pub use crate::sys::handle_request;
27pub use crate::sys::handle_request_with_timeout;
28use crate::BatControlCommand;
29use crate::BatControlResult;
30use crate::BatteryType;
31#[cfg(feature = "audio")]
32use crate::SndControlCommand;
33use crate::SwapCommand;
34use crate::UsbControlCommand;
35use crate::UsbControlResult;
36use crate::VmRequest;
37use crate::VmResponse;
38use crate::USB_CONTROL_MAX_PORTS;
39
40#[sorted]
41#[derive(Error, Debug)]
42enum ModifyBatError {
43    #[error("{0}")]
44    BatControlErr(BatControlResult),
45}
46
47#[sorted]
48#[derive(Error, Debug)]
49pub enum ModifyUsbError {
50    #[error("failed to open device {0}: {1}")]
51    FailedToOpenDevice(PathBuf, base::Error),
52    #[error("socket failed")]
53    SocketFailed,
54    #[error("unexpected response: {0}")]
55    UnexpectedResponse(VmResponse),
56    #[error("{0}")]
57    UsbControl(UsbControlResult),
58}
59
60pub type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
61
62pub type VmsRequestResult = std::result::Result<(), ()>;
63
64/// Send a `VmRequest` that expects a `VmResponse::Ok` reply.
65pub fn vms_request<T: AsRef<Path> + std::fmt::Debug>(
66    request: &VmRequest,
67    socket_path: T,
68) -> VmsRequestResult {
69    match handle_request(request, socket_path)? {
70        VmResponse::Ok => Ok(()),
71        r => {
72            println!("unexpected response: {r}");
73            Err(())
74        }
75    }
76}
77
78#[cfg(feature = "pci-hotplug")]
79/// Send a `VmRequest` for PCI hotplug that expects `VmResponse::PciResponse::AddOk(bus)`
80pub fn do_net_add<T: AsRef<Path> + std::fmt::Debug>(
81    tap_name: &str,
82    socket_path: T,
83) -> AnyHowResult<u8> {
84    let request =
85        VmRequest::HotPlugNetCommand(crate::NetControlCommand::AddTap(tap_name.to_owned()));
86    let response = handle_request(&request, socket_path).map_err(|()| anyhow!("socket error: "))?;
87    match response {
88        VmResponse::PciHotPlugResponse { bus } => Ok(bus),
89        e => Err(anyhow!("Unexpected response: {:#}", e)),
90    }
91}
92
93#[cfg(not(feature = "pci-hotplug"))]
94/// Send a `VmRequest` for PCI hotplug that expects `VmResponse::PciResponse::AddOk(bus)`
95pub fn do_net_add<T: AsRef<Path> + std::fmt::Debug>(
96    _tap_name: &str,
97    _socket_path: T,
98) -> AnyHowResult<u8> {
99    anyhow::bail!("Unsupported: pci-hotplug feature disabled");
100}
101
102#[cfg(feature = "pci-hotplug")]
103/// Send a `VmRequest` for removing hotplugged PCI device that expects `VmResponse::Ok`
104pub fn do_net_remove<T: AsRef<Path> + std::fmt::Debug>(
105    bus_num: u8,
106    socket_path: T,
107) -> AnyHowResult<()> {
108    let request = VmRequest::HotPlugNetCommand(crate::NetControlCommand::RemoveTap(bus_num));
109    let response = handle_request(&request, socket_path).map_err(|()| anyhow!("socket error: "))?;
110    match response {
111        VmResponse::Ok => Ok(()),
112        e => Err(anyhow!("Unexpected response: {:#}", e)),
113    }
114}
115
116#[cfg(not(feature = "pci-hotplug"))]
117/// Send a `VmRequest` for removing hotplugged PCI device that expects `VmResponse::Ok`
118pub fn do_net_remove<T: AsRef<Path> + std::fmt::Debug>(
119    _bus_num: u8,
120    _socket_path: T,
121) -> AnyHowResult<()> {
122    anyhow::bail!("Unsupported: pci-hotplug feature disabled");
123}
124
125pub fn do_usb_attach<T: AsRef<Path> + std::fmt::Debug>(
126    socket_path: T,
127    dev_path: &Path,
128) -> ModifyUsbResult<UsbControlResult> {
129    let usb_file = open_file_or_duplicate(dev_path, OpenOptions::new().read(true).write(true))
130        .map_err(|e| ModifyUsbError::FailedToOpenDevice(dev_path.into(), e))?;
131
132    let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice { file: usb_file });
133    let response =
134        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
135    match response {
136        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
137        r => Err(ModifyUsbError::UnexpectedResponse(r)),
138    }
139}
140
141pub fn do_security_key_attach<T: AsRef<Path> + std::fmt::Debug>(
142    socket_path: T,
143    dev_path: &Path,
144) -> ModifyUsbResult<UsbControlResult> {
145    let usb_file = open_file_or_duplicate(dev_path, OpenOptions::new().read(true).write(true))
146        .map_err(|e| ModifyUsbError::FailedToOpenDevice(dev_path.into(), e))?;
147
148    let request = VmRequest::UsbCommand(UsbControlCommand::AttachSecurityKey { file: usb_file });
149    let response =
150        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
151    match response {
152        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
153        r => Err(ModifyUsbError::UnexpectedResponse(r)),
154    }
155}
156
157pub fn do_usb_detach<T: AsRef<Path> + std::fmt::Debug>(
158    socket_path: T,
159    port: u8,
160) -> ModifyUsbResult<UsbControlResult> {
161    let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
162    let response =
163        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
164    match response {
165        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
166        r => Err(ModifyUsbError::UnexpectedResponse(r)),
167    }
168}
169
170pub fn do_usb_list<T: AsRef<Path> + std::fmt::Debug>(
171    socket_path: T,
172) -> ModifyUsbResult<UsbControlResult> {
173    let mut ports: [u8; USB_CONTROL_MAX_PORTS] = Default::default();
174    for (index, port) in ports.iter_mut().enumerate() {
175        *port = index as u8
176    }
177    let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { ports });
178    let response =
179        handle_request(&request, socket_path).map_err(|_| ModifyUsbError::SocketFailed)?;
180    match response {
181        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
182        r => Err(ModifyUsbError::UnexpectedResponse(r)),
183    }
184}
185
186pub type DoModifyBatteryResult = std::result::Result<(), ()>;
187
188pub fn do_modify_battery<T: AsRef<Path> + std::fmt::Debug>(
189    socket_path: T,
190    battery_type: &str,
191    property: &str,
192    target: &str,
193) -> DoModifyBatteryResult {
194    let response = match battery_type.parse::<BatteryType>() {
195        Ok(type_) => match BatControlCommand::new(property.to_string(), target.to_string()) {
196            Ok(cmd) => {
197                let request = VmRequest::BatCommand(type_, cmd);
198                Ok(handle_request(&request, socket_path)?)
199            }
200            Err(e) => Err(ModifyBatError::BatControlErr(e)),
201        },
202        Err(e) => Err(ModifyBatError::BatControlErr(e)),
203    };
204
205    match response {
206        Ok(response) => {
207            println!("{response}");
208            Ok(())
209        }
210        Err(e) => {
211            println!("error {e}");
212            Err(())
213        }
214    }
215}
216
217#[cfg(feature = "audio")]
218/// Send a `VmRequest` for muting/unmuting snd all snd devices`
219pub fn do_snd_mute_all<T: AsRef<Path> + std::fmt::Debug>(
220    socket_path: T,
221    muted: bool,
222) -> std::result::Result<(), ()> {
223    let request = VmRequest::SndCommand(SndControlCommand::MuteAll(muted));
224    let response = handle_request(&request, socket_path)?;
225    match response {
226        VmResponse::Ok => Ok(()),
227        e => {
228            println!("Unexpected response: {e:#}");
229            Err(())
230        }
231    }
232}
233
234#[cfg(not(feature = "audio"))]
235/// Send a `VmRequest` for muting/unmuting snd all snd devices`
236pub fn do_snd_mute_all<T: AsRef<Path> + std::fmt::Debug>(
237    _socket_path: T,
238    _muted: bool,
239) -> std::result::Result<(), ()> {
240    println!("Unsupported: audio feature disabled");
241    Err(())
242}
243
244pub fn do_swap_status<T: AsRef<Path> + std::fmt::Debug>(socket_path: T) -> VmsRequestResult {
245    let response = handle_request(&VmRequest::Swap(SwapCommand::Status), socket_path)?;
246    match &response {
247        VmResponse::SwapStatus(_) => {
248            println!("{response}");
249            Ok(())
250        }
251        r => {
252            println!("unexpected response: {r:?}");
253            Err(())
254        }
255    }
256}
257
258pub type HandleRequestResult = std::result::Result<VmResponse, ()>;