1 // Copyright 2021 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // Provides parts of crosvm as a library to communicate with running crosvm instances.
6 // Usually you would need to invoke crosvm with subcommands and you'd get the result on
7 // stdout.
8 use std::convert::{TryFrom, TryInto};
9 use std::ffi::CStr;
10 use std::panic::catch_unwind;
11 use std::path::{Path, PathBuf};
12
13 use libc::{c_char, ssize_t};
14
15 use vm_control::{
16 client::*, BalloonControlCommand, BalloonStats, DiskControlCommand, UsbControlAttachedDevice,
17 UsbControlResult, VmRequest, VmResponse,
18 };
19
validate_socket_path(socket_path: *const c_char) -> Option<PathBuf>20 fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> {
21 if !socket_path.is_null() {
22 let socket_path = unsafe { CStr::from_ptr(socket_path) };
23 Some(PathBuf::from(socket_path.to_str().ok()?))
24 } else {
25 None
26 }
27 }
28
29 /// Stops the crosvm instance whose control socket is listening on `socket_path`.
30 ///
31 /// The function returns true on success or false if an error occured.
32 #[no_mangle]
crosvm_client_stop_vm(socket_path: *const c_char) -> bool33 pub extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool {
34 catch_unwind(|| {
35 if let Some(socket_path) = validate_socket_path(socket_path) {
36 vms_request(&VmRequest::Exit, &socket_path).is_ok()
37 } else {
38 false
39 }
40 })
41 .unwrap_or(false)
42 }
43
44 /// Suspends the crosvm instance whose control socket is listening on `socket_path`.
45 ///
46 /// The function returns true on success or false if an error occured.
47 #[no_mangle]
crosvm_client_suspend_vm(socket_path: *const c_char) -> bool48 pub extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool {
49 catch_unwind(|| {
50 if let Some(socket_path) = validate_socket_path(socket_path) {
51 vms_request(&VmRequest::Suspend, &socket_path).is_ok()
52 } else {
53 false
54 }
55 })
56 .unwrap_or(false)
57 }
58
59 /// Resumes the crosvm instance whose control socket is listening on `socket_path`.
60 ///
61 /// The function returns true on success or false if an error occured.
62 #[no_mangle]
crosvm_client_resume_vm(socket_path: *const c_char) -> bool63 pub extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool {
64 catch_unwind(|| {
65 if let Some(socket_path) = validate_socket_path(socket_path) {
66 vms_request(&VmRequest::Resume, &socket_path).is_ok()
67 } else {
68 false
69 }
70 })
71 .unwrap_or(false)
72 }
73
74 /// Adjusts the balloon size of the crosvm instance whose control socket is
75 /// listening on `socket_path`.
76 ///
77 /// The function returns true on success or false if an error occured.
78 #[no_mangle]
crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool79 pub extern "C" fn crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool {
80 catch_unwind(|| {
81 if let Some(socket_path) = validate_socket_path(socket_path) {
82 let command = BalloonControlCommand::Adjust { num_bytes };
83 vms_request(&VmRequest::BalloonCommand(command), &socket_path).is_ok()
84 } else {
85 false
86 }
87 })
88 .unwrap_or(false)
89 }
90
91 /// Represents an individual attached USB device.
92 #[repr(C)]
93 pub struct UsbDeviceEntry {
94 /// Internal port index used for identifying this individual device.
95 port: u8,
96 /// USB vendor ID
97 vendor_id: u16,
98 /// USB product ID
99 product_id: u16,
100 }
101
102 impl From<&UsbControlAttachedDevice> for UsbDeviceEntry {
from(other: &UsbControlAttachedDevice) -> Self103 fn from(other: &UsbControlAttachedDevice) -> Self {
104 Self {
105 port: other.port,
106 vendor_id: other.vendor_id,
107 product_id: other.product_id,
108 }
109 }
110 }
111
112 /// Returns all USB devices passed through the crosvm instance whose control socket is listening on `socket_path`.
113 ///
114 /// The function returns the amount of entries written.
115 /// # Arguments
116 ///
117 /// * `socket_path` - Path to the crosvm control socket
118 /// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached
119 /// devices will be written to
120 /// * `entries_length` - Amount of entries in the array specified by `entries`
121 ///
122 /// Crosvm supports passing through up to 255 devices, so pasing an array with 255 entries will
123 /// guarantee to return all entries.
124 #[no_mangle]
crosvm_client_usb_list( socket_path: *const c_char, entries: *mut UsbDeviceEntry, entries_length: ssize_t, ) -> ssize_t125 pub extern "C" fn crosvm_client_usb_list(
126 socket_path: *const c_char,
127 entries: *mut UsbDeviceEntry,
128 entries_length: ssize_t,
129 ) -> ssize_t {
130 catch_unwind(|| {
131 if let Some(socket_path) = validate_socket_path(socket_path) {
132 if let Ok(UsbControlResult::Devices(res)) = do_usb_list(&socket_path) {
133 let mut i = 0;
134 for entry in res.iter().filter(|x| x.valid()) {
135 if i >= entries_length {
136 break;
137 }
138 unsafe {
139 *entries.offset(i) = entry.into();
140 i += 1;
141 }
142 }
143 i
144 } else {
145 -1
146 }
147 } else {
148 -1
149 }
150 })
151 .unwrap_or(-1)
152 }
153
154 /// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`.
155 ///
156 /// The function returns the amount of entries written.
157 /// # Arguments
158 ///
159 /// * `socket_path` - Path to the crosvm control socket
160 /// * `bus` - USB device bus ID
161 /// * `addr` - USB device address
162 /// * `vid` - USB device vendor ID
163 /// * `pid` - USB device product ID
164 /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`).
165 /// * `out_port` - (optional) internal port will be written here if provided.
166 ///
167 /// The function returns true on success or false if an error occured.
168 #[no_mangle]
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, ) -> bool169 pub extern "C" fn crosvm_client_usb_attach(
170 socket_path: *const c_char,
171 bus: u8,
172 addr: u8,
173 vid: u16,
174 pid: u16,
175 dev_path: *const c_char,
176 out_port: *mut u8,
177 ) -> bool {
178 catch_unwind(|| {
179 if let Some(socket_path) = validate_socket_path(socket_path) {
180 if dev_path.is_null() {
181 return false;
182 }
183 let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or(""));
184
185 if let Ok(UsbControlResult::Ok { port }) =
186 do_usb_attach(&socket_path, bus, addr, vid, pid, dev_path)
187 {
188 if !out_port.is_null() {
189 unsafe { *out_port = port };
190 }
191 true
192 } else {
193 false
194 }
195 } else {
196 false
197 }
198 })
199 .unwrap_or(false)
200 }
201
202 /// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`.
203 /// `port` determines device to be detached.
204 ///
205 /// The function returns true on success or false if an error occured.
206 #[no_mangle]
crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool207 pub extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool {
208 catch_unwind(|| {
209 if let Some(socket_path) = validate_socket_path(socket_path) {
210 do_usb_detach(&socket_path, port).is_ok()
211 } else {
212 false
213 }
214 })
215 .unwrap_or(false)
216 }
217
218 /// Modifies the battery status of crosvm instance whose control socket is listening on
219 /// `socket_path`.
220 ///
221 /// The function returns true on success or false if an error occured.
222 #[no_mangle]
crosvm_client_modify_battery( socket_path: *const c_char, battery_type: *const c_char, property: *const c_char, target: *const c_char, ) -> bool223 pub extern "C" fn crosvm_client_modify_battery(
224 socket_path: *const c_char,
225 battery_type: *const c_char,
226 property: *const c_char,
227 target: *const c_char,
228 ) -> bool {
229 catch_unwind(|| {
230 if let Some(socket_path) = validate_socket_path(socket_path) {
231 if battery_type.is_null() || property.is_null() || target.is_null() {
232 return false;
233 }
234 let battery_type = unsafe { CStr::from_ptr(battery_type) };
235 let property = unsafe { CStr::from_ptr(property) };
236 let target = unsafe { CStr::from_ptr(target) };
237
238 do_modify_battery(
239 &socket_path,
240 battery_type.to_str().unwrap(),
241 property.to_str().unwrap(),
242 target.to_str().unwrap(),
243 )
244 .is_ok()
245 } else {
246 false
247 }
248 })
249 .unwrap_or(false)
250 }
251
252 /// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`.
253 ///
254 /// The function returns true on success or false if an error occured.
255 #[no_mangle]
crosvm_client_resize_disk( socket_path: *const c_char, disk_index: u64, new_size: u64, ) -> bool256 pub extern "C" fn crosvm_client_resize_disk(
257 socket_path: *const c_char,
258 disk_index: u64,
259 new_size: u64,
260 ) -> bool {
261 catch_unwind(|| {
262 if let Some(socket_path) = validate_socket_path(socket_path) {
263 if let Ok(disk_index) = usize::try_from(disk_index) {
264 let request = VmRequest::DiskCommand {
265 disk_index,
266 command: DiskControlCommand::Resize { new_size },
267 };
268 vms_request(&request, &socket_path).is_ok()
269 } else {
270 false
271 }
272 } else {
273 false
274 }
275 })
276 .unwrap_or(false)
277 }
278
279 /// Similar to internally used `BalloonStats` but using i64 instead of
280 /// Option<u64>. `None` (or values bigger than i64::max) will be encoded as -1.
281 #[repr(C)]
282 pub struct BalloonStatsFfi {
283 swap_in: i64,
284 swap_out: i64,
285 major_faults: i64,
286 minor_faults: i64,
287 free_memory: i64,
288 total_memory: i64,
289 available_memory: i64,
290 disk_caches: i64,
291 hugetlb_allocations: i64,
292 hugetlb_failures: i64,
293 }
294
295 impl From<&BalloonStats> for BalloonStatsFfi {
from(other: &BalloonStats) -> Self296 fn from(other: &BalloonStats) -> Self {
297 let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
298 Self {
299 swap_in: convert(other.swap_in),
300 swap_out: convert(other.swap_out),
301 major_faults: convert(other.major_faults),
302 minor_faults: convert(other.minor_faults),
303 free_memory: convert(other.free_memory),
304 total_memory: convert(other.total_memory),
305 available_memory: convert(other.available_memory),
306 disk_caches: convert(other.disk_caches),
307 hugetlb_allocations: convert(other.hugetlb_allocations),
308 hugetlb_failures: convert(other.hugetlb_failures),
309 }
310 }
311 }
312
313 /// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`.
314 ///
315 /// The parameters `stats` and `actual` are optional and will only be written to if they are
316 /// non-null.
317 ///
318 /// The function returns true on success or false if an error occured.
319 ///
320 /// # Note
321 ///
322 /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`.
323 #[no_mangle]
crosvm_client_balloon_stats( socket_path: *const c_char, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool324 pub extern "C" fn crosvm_client_balloon_stats(
325 socket_path: *const c_char,
326 stats: *mut BalloonStatsFfi,
327 actual: *mut u64,
328 ) -> bool {
329 catch_unwind(|| {
330 if let Some(socket_path) = validate_socket_path(socket_path) {
331 let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
332 if let Ok(VmResponse::BalloonStats {
333 stats: ref balloon_stats,
334 balloon_actual,
335 }) = handle_request(request, &socket_path)
336 {
337 if !stats.is_null() {
338 unsafe {
339 *stats = balloon_stats.into();
340 }
341 }
342
343 if !actual.is_null() {
344 unsafe {
345 *actual = balloon_actual;
346 }
347 }
348 true
349 } else {
350 false
351 }
352 } else {
353 false
354 }
355 })
356 .unwrap_or(false)
357 }
358