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
5 //! Provides parts of crosvm as a library to communicate with running crosvm instances.
6 //!
7 //! This crate is a programmatic alternative to invoking crosvm with subcommands that produce the
8 //! result on stdout.
9 //!
10 //! Downstream projects rely on this library maintaining a stable API surface.
11 //! Do not make changes to this library without consulting the crosvm externalization team.
12 //! Email: crosvm-dev@chromium.org
13 //! For more information see:
14 //! <https://crosvm.dev/book/running_crosvm/programmatic_interaction.html#usage>
15
16 use std::convert::TryFrom;
17 use std::convert::TryInto;
18 use std::ffi::CStr;
19 use std::panic::catch_unwind;
20 use std::path::Path;
21 use std::path::PathBuf;
22
23 use libc::c_char;
24 use libc::ssize_t;
25 use vm_control::client::*;
26 use vm_control::BalloonControlCommand;
27 use vm_control::BalloonStats;
28 use vm_control::BalloonWSS;
29 use vm_control::DiskControlCommand;
30 use vm_control::RegisteredEvent;
31 use vm_control::UsbControlAttachedDevice;
32 use vm_control::UsbControlResult;
33 use vm_control::VmRequest;
34 use vm_control::VmResponse;
35 use vm_control::WSSBucket;
36 use vm_control::USB_CONTROL_MAX_PORTS;
37
validate_socket_path(socket_path: *const c_char) -> Option<PathBuf>38 fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> {
39 if !socket_path.is_null() {
40 let socket_path = unsafe { CStr::from_ptr(socket_path) };
41 Some(PathBuf::from(socket_path.to_str().ok()?))
42 } else {
43 None
44 }
45 }
46
47 /// Stops the crosvm instance whose control socket is listening on `socket_path`.
48 ///
49 /// The function returns true on success or false if an error occured.
50 #[no_mangle]
crosvm_client_stop_vm(socket_path: *const c_char) -> bool51 pub extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool {
52 catch_unwind(|| {
53 if let Some(socket_path) = validate_socket_path(socket_path) {
54 vms_request(&VmRequest::Exit, &socket_path).is_ok()
55 } else {
56 false
57 }
58 })
59 .unwrap_or(false)
60 }
61
62 /// Suspends the crosvm instance whose control socket is listening on `socket_path`.
63 ///
64 /// The function returns true on success or false if an error occured.
65 #[no_mangle]
crosvm_client_suspend_vm(socket_path: *const c_char) -> bool66 pub extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool {
67 catch_unwind(|| {
68 if let Some(socket_path) = validate_socket_path(socket_path) {
69 vms_request(&VmRequest::Suspend, &socket_path).is_ok()
70 } else {
71 false
72 }
73 })
74 .unwrap_or(false)
75 }
76
77 /// Resumes the crosvm instance whose control socket is listening on `socket_path`.
78 ///
79 /// The function returns true on success or false if an error occured.
80 #[no_mangle]
crosvm_client_resume_vm(socket_path: *const c_char) -> bool81 pub extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool {
82 catch_unwind(|| {
83 if let Some(socket_path) = validate_socket_path(socket_path) {
84 vms_request(&VmRequest::Resume, &socket_path).is_ok()
85 } else {
86 false
87 }
88 })
89 .unwrap_or(false)
90 }
91
92 /// Creates an RT vCPU for the crosvm instance whose control socket is listening on `socket_path`.
93 ///
94 /// The function returns true on success or false if an error occured.
95 #[no_mangle]
crosvm_client_make_rt_vm(socket_path: *const c_char) -> bool96 pub extern "C" fn crosvm_client_make_rt_vm(socket_path: *const c_char) -> bool {
97 catch_unwind(|| {
98 if let Some(socket_path) = validate_socket_path(socket_path) {
99 vms_request(&VmRequest::MakeRT, &socket_path).is_ok()
100 } else {
101 false
102 }
103 })
104 .unwrap_or(false)
105 }
106
107 /// Adjusts the balloon size of the crosvm instance whose control socket is
108 /// listening on `socket_path`.
109 ///
110 /// The function returns true on success or false if an error occured.
111 #[no_mangle]
crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool112 pub extern "C" fn crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool {
113 catch_unwind(|| {
114 if let Some(socket_path) = validate_socket_path(socket_path) {
115 let command = BalloonControlCommand::Adjust { num_bytes };
116 vms_request(&VmRequest::BalloonCommand(command), &socket_path).is_ok()
117 } else {
118 false
119 }
120 })
121 .unwrap_or(false)
122 }
123
124 /// Enable vmm swap for crosvm instance whose control socket is listening on `socket_path`.
125 ///
126 /// The function returns true on success or false if an error occured.
127 #[no_mangle]
crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool128 pub extern "C" fn crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool {
129 catch_unwind(|| {
130 if let Some(socket_path) = validate_socket_path(socket_path) {
131 vms_request(&VmRequest::Swap(SwapCommand::Enable), &socket_path).is_ok()
132 } else {
133 false
134 }
135 })
136 .unwrap_or(false)
137 }
138
139 /// Swap out staging memory for crosvm instance whose control socket is listening
140 /// on `socket_path`.
141 ///
142 /// The function returns true on success or false if an error occured.
143 #[no_mangle]
crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool144 pub extern "C" fn crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool {
145 catch_unwind(|| {
146 if let Some(socket_path) = validate_socket_path(socket_path) {
147 vms_request(&VmRequest::Swap(SwapCommand::SwapOut), &socket_path).is_ok()
148 } else {
149 false
150 }
151 })
152 .unwrap_or(false)
153 }
154
155 /// Disable vmm swap for crosvm instance whose control socket is listening on `socket_path`.
156 ///
157 /// The function returns true on success or false if an error occured.
158 #[no_mangle]
crosvm_client_swap_disable_vm(socket_path: *const c_char) -> bool159 pub extern "C" fn crosvm_client_swap_disable_vm(socket_path: *const c_char) -> bool {
160 catch_unwind(|| {
161 if let Some(socket_path) = validate_socket_path(socket_path) {
162 vms_request(&VmRequest::Swap(SwapCommand::Disable), &socket_path).is_ok()
163 } else {
164 false
165 }
166 })
167 .unwrap_or(false)
168 }
169
170 /// Represents an individual attached USB device.
171 #[repr(C)]
172 pub struct UsbDeviceEntry {
173 /// Internal port index used for identifying this individual device.
174 port: u8,
175 /// USB vendor ID
176 vendor_id: u16,
177 /// USB product ID
178 product_id: u16,
179 }
180
181 impl From<&UsbControlAttachedDevice> for UsbDeviceEntry {
from(other: &UsbControlAttachedDevice) -> Self182 fn from(other: &UsbControlAttachedDevice) -> Self {
183 Self {
184 port: other.port,
185 vendor_id: other.vendor_id,
186 product_id: other.product_id,
187 }
188 }
189 }
190
191 /// Simply returns the maximum possible number of USB devices
192 #[no_mangle]
crosvm_client_max_usb_devices() -> usize193 pub extern "C" fn crosvm_client_max_usb_devices() -> usize {
194 USB_CONTROL_MAX_PORTS
195 }
196
197 /// Returns all USB devices passed through the crosvm instance whose control socket is listening on `socket_path`.
198 ///
199 /// The function returns the amount of entries written.
200 /// # Arguments
201 ///
202 /// * `socket_path` - Path to the crosvm control socket
203 /// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached
204 /// devices will be written to
205 /// * `entries_length` - Amount of entries in the array specified by `entries`
206 ///
207 /// Use the value returned by [`crosvm_client_max_usb_devices()`] to determine the size of the input
208 /// array to this function.
209 #[no_mangle]
crosvm_client_usb_list( socket_path: *const c_char, entries: *mut UsbDeviceEntry, entries_length: ssize_t, ) -> ssize_t210 pub extern "C" fn crosvm_client_usb_list(
211 socket_path: *const c_char,
212 entries: *mut UsbDeviceEntry,
213 entries_length: ssize_t,
214 ) -> ssize_t {
215 catch_unwind(|| {
216 if let Some(socket_path) = validate_socket_path(socket_path) {
217 if let Ok(UsbControlResult::Devices(res)) = do_usb_list(&socket_path) {
218 let mut i = 0;
219 for entry in res.iter().filter(|x| x.valid()) {
220 if i >= entries_length {
221 break;
222 }
223 unsafe {
224 *entries.offset(i) = entry.into();
225 i += 1;
226 }
227 }
228 i
229 } else {
230 -1
231 }
232 } else {
233 -1
234 }
235 })
236 .unwrap_or(-1)
237 }
238
239 /// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`.
240 ///
241 /// The function returns the amount of entries written.
242 /// # Arguments
243 ///
244 /// * `socket_path` - Path to the crosvm control socket
245 /// * `bus` - USB device bus ID (unused)
246 /// * `addr` - USB device address (unused)
247 /// * `vid` - USB device vendor ID (unused)
248 /// * `pid` - USB device product ID (unused)
249 /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`).
250 /// * `out_port` - (optional) internal port will be written here if provided.
251 ///
252 /// The function returns true on success or false if an error occured.
253 #[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, ) -> bool254 pub extern "C" fn crosvm_client_usb_attach(
255 socket_path: *const c_char,
256 _bus: u8,
257 _addr: u8,
258 _vid: u16,
259 _pid: u16,
260 dev_path: *const c_char,
261 out_port: *mut u8,
262 ) -> bool {
263 catch_unwind(|| {
264 if let Some(socket_path) = validate_socket_path(socket_path) {
265 if dev_path.is_null() {
266 return false;
267 }
268 let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or(""));
269
270 if let Ok(UsbControlResult::Ok { port }) = do_usb_attach(&socket_path, dev_path) {
271 if !out_port.is_null() {
272 unsafe { *out_port = port };
273 }
274 true
275 } else {
276 false
277 }
278 } else {
279 false
280 }
281 })
282 .unwrap_or(false)
283 }
284
285 /// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`.
286 /// `port` determines device to be detached.
287 ///
288 /// The function returns true on success or false if an error occured.
289 #[no_mangle]
crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool290 pub extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool {
291 catch_unwind(|| {
292 if let Some(socket_path) = validate_socket_path(socket_path) {
293 do_usb_detach(&socket_path, port).is_ok()
294 } else {
295 false
296 }
297 })
298 .unwrap_or(false)
299 }
300
301 /// Modifies the battery status of crosvm instance whose control socket is listening on
302 /// `socket_path`.
303 ///
304 /// The function returns true on success or false if an error occured.
305 #[no_mangle]
crosvm_client_modify_battery( socket_path: *const c_char, battery_type: *const c_char, property: *const c_char, target: *const c_char, ) -> bool306 pub extern "C" fn crosvm_client_modify_battery(
307 socket_path: *const c_char,
308 battery_type: *const c_char,
309 property: *const c_char,
310 target: *const c_char,
311 ) -> bool {
312 catch_unwind(|| {
313 if let Some(socket_path) = validate_socket_path(socket_path) {
314 if battery_type.is_null() || property.is_null() || target.is_null() {
315 return false;
316 }
317 let battery_type = unsafe { CStr::from_ptr(battery_type) };
318 let property = unsafe { CStr::from_ptr(property) };
319 let target = unsafe { CStr::from_ptr(target) };
320
321 do_modify_battery(
322 &socket_path,
323 battery_type.to_str().unwrap(),
324 property.to_str().unwrap(),
325 target.to_str().unwrap(),
326 )
327 .is_ok()
328 } else {
329 false
330 }
331 })
332 .unwrap_or(false)
333 }
334
335 /// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`.
336 ///
337 /// The function returns true on success or false if an error occured.
338 #[no_mangle]
crosvm_client_resize_disk( socket_path: *const c_char, disk_index: u64, new_size: u64, ) -> bool339 pub extern "C" fn crosvm_client_resize_disk(
340 socket_path: *const c_char,
341 disk_index: u64,
342 new_size: u64,
343 ) -> bool {
344 catch_unwind(|| {
345 if let Some(socket_path) = validate_socket_path(socket_path) {
346 if let Ok(disk_index) = usize::try_from(disk_index) {
347 let request = VmRequest::DiskCommand {
348 disk_index,
349 command: DiskControlCommand::Resize { new_size },
350 };
351 vms_request(&request, &socket_path).is_ok()
352 } else {
353 false
354 }
355 } else {
356 false
357 }
358 })
359 .unwrap_or(false)
360 }
361
362 /// Similar to internally used `BalloonStats` but using i64 instead of
363 /// Option<u64>. `None` (or values bigger than i64::max) will be encoded as -1.
364 #[repr(C)]
365 pub struct BalloonStatsFfi {
366 swap_in: i64,
367 swap_out: i64,
368 major_faults: i64,
369 minor_faults: i64,
370 free_memory: i64,
371 total_memory: i64,
372 available_memory: i64,
373 disk_caches: i64,
374 hugetlb_allocations: i64,
375 hugetlb_failures: i64,
376 shared_memory: i64,
377 unevictable_memory: i64,
378 }
379
380 impl From<&BalloonStats> for BalloonStatsFfi {
from(other: &BalloonStats) -> Self381 fn from(other: &BalloonStats) -> Self {
382 let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
383 Self {
384 swap_in: convert(other.swap_in),
385 swap_out: convert(other.swap_out),
386 major_faults: convert(other.major_faults),
387 minor_faults: convert(other.minor_faults),
388 free_memory: convert(other.free_memory),
389 total_memory: convert(other.total_memory),
390 available_memory: convert(other.available_memory),
391 disk_caches: convert(other.disk_caches),
392 hugetlb_allocations: convert(other.hugetlb_allocations),
393 hugetlb_failures: convert(other.hugetlb_failures),
394 shared_memory: convert(other.shared_memory),
395 unevictable_memory: convert(other.unevictable_memory),
396 }
397 }
398 }
399
400 /// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`.
401 ///
402 /// The parameters `stats` and `actual` are optional and will only be written to if they are
403 /// non-null.
404 ///
405 /// The function returns true on success or false if an error occured.
406 ///
407 /// # Note
408 ///
409 /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`.
410 #[no_mangle]
crosvm_client_balloon_stats( socket_path: *const c_char, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool411 pub extern "C" fn crosvm_client_balloon_stats(
412 socket_path: *const c_char,
413 stats: *mut BalloonStatsFfi,
414 actual: *mut u64,
415 ) -> bool {
416 catch_unwind(|| {
417 if let Some(socket_path) = validate_socket_path(socket_path) {
418 let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
419 if let Ok(VmResponse::BalloonStats {
420 stats: ref balloon_stats,
421 balloon_actual,
422 }) = handle_request(request, &socket_path)
423 {
424 if !stats.is_null() {
425 unsafe {
426 *stats = balloon_stats.into();
427 }
428 }
429
430 if !actual.is_null() {
431 unsafe {
432 *actual = balloon_actual;
433 }
434 }
435 true
436 } else {
437 false
438 }
439 } else {
440 false
441 }
442 })
443 .unwrap_or(false)
444 }
445
446 /// Externally exposed variant of BalloonWss/WSSBucket, used for FFI.
447 #[derive(Clone, Copy, Debug)]
448 #[repr(C)]
449 pub struct WSSBucketFfi {
450 age: u64,
451 bytes: [u64; 2],
452 }
453
454 impl WSSBucketFfi {
new() -> Self455 fn new() -> Self {
456 Self {
457 age: 0,
458 bytes: [0, 0],
459 }
460 }
461 }
462
463 impl From<WSSBucket> for WSSBucketFfi {
from(other: WSSBucket) -> Self464 fn from(other: WSSBucket) -> Self {
465 Self {
466 age: other.age,
467 bytes: other.bytes,
468 }
469 }
470 }
471
472 #[repr(C)]
473 #[derive(Debug)]
474 pub struct BalloonWSSFfi {
475 wss: [WSSBucketFfi; 4],
476 }
477
478 impl From<&BalloonWSS> for BalloonWSSFfi {
from(other: &BalloonWSS) -> Self479 fn from(other: &BalloonWSS) -> Self {
480 let mut ffi = Self {
481 wss: [WSSBucketFfi::new(); 4],
482 };
483 for (ffi_wss, other_wss) in ffi.wss.iter_mut().zip(other.wss) {
484 *ffi_wss = other_wss.into();
485 }
486 ffi
487 }
488 }
489
490 impl BalloonWSSFfi {
new() -> Self491 pub fn new() -> Self {
492 Self {
493 wss: [WSSBucketFfi::new(); 4],
494 }
495 }
496 }
497
498 /// Returns balloon working set size of the crosvm instance whose control socket is listening on socket_path.
499 #[no_mangle]
crosvm_client_balloon_wss( socket_path: *const c_char, wss: *mut BalloonWSSFfi, ) -> bool500 pub extern "C" fn crosvm_client_balloon_wss(
501 socket_path: *const c_char,
502 wss: *mut BalloonWSSFfi,
503 ) -> bool {
504 catch_unwind(|| {
505 if let Some(socket_path) = validate_socket_path(socket_path) {
506 let request = &VmRequest::BalloonCommand(BalloonControlCommand::WorkingSetSize);
507 if let Ok(VmResponse::BalloonWSS {
508 wss: ref balloon_wss,
509 }) = handle_request(request, &socket_path)
510 {
511 if !wss.is_null() {
512 // SAFETY: deref of raw pointer is safe because we check to
513 // make sure the pointer is not null.
514 unsafe {
515 *wss = balloon_wss.into();
516 }
517 }
518 true
519 } else {
520 false
521 }
522 } else {
523 false
524 }
525 })
526 .unwrap_or(false)
527 }
528
529 /// Publically exposed version of RegisteredEvent enum, implemented as an
530 /// integral newtype for FFI safety.
531 #[repr(C)]
532 #[derive(Copy, Clone, PartialEq, Eq)]
533 pub struct RegisteredEventFfi(u32);
534
535 pub const REGISTERED_EVENT_VIRTIO_BALLOON_WSS_REPORT: RegisteredEventFfi = RegisteredEventFfi(0);
536 pub const REGISTERED_EVENT_VIRTIO_BALLOON_RESIZE: RegisteredEventFfi = RegisteredEventFfi(1);
537 pub const REGISTERED_EVENT_VIRTIO_BALLOON_OOM_DEFLATION: RegisteredEventFfi = RegisteredEventFfi(2);
538
539 impl TryFrom<RegisteredEventFfi> for RegisteredEvent {
540 type Error = &'static str;
541
try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error>542 fn try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error> {
543 match value.0 {
544 0 => Ok(RegisteredEvent::VirtioBalloonWssReport),
545 1 => Ok(RegisteredEvent::VirtioBalloonResize),
546 2 => Ok(RegisteredEvent::VirtioBalloonOOMDeflation),
547 _ => Err("RegisteredEventFFi outside of known RegisteredEvent enum range"),
548 }
549 }
550 }
551
552 /// Registers the connected process as a listener for `event`.
553 #[no_mangle]
crosvm_client_register_events_listener( socket_path: *const c_char, listening_socket_path: *const c_char, event: RegisteredEventFfi, ) -> bool554 pub extern "C" fn crosvm_client_register_events_listener(
555 socket_path: *const c_char,
556 listening_socket_path: *const c_char,
557 event: RegisteredEventFfi,
558 ) -> bool {
559 catch_unwind(|| {
560 if let Some(socket_path) = validate_socket_path(socket_path) {
561 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
562 if let Ok(event) = event.try_into() {
563 let request = VmRequest::RegisterListener {
564 event,
565 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
566 };
567 vms_request(&request, &socket_path).is_ok()
568 } else {
569 false
570 }
571 } else {
572 false
573 }
574 } else {
575 false
576 }
577 })
578 .unwrap_or(false)
579 }
580
581 /// Unegisters the connected process as a listener for `event`.
582 #[no_mangle]
crosvm_client_unregister_events_listener( socket_path: *const c_char, listening_socket_path: *const c_char, event: RegisteredEventFfi, ) -> bool583 pub extern "C" fn crosvm_client_unregister_events_listener(
584 socket_path: *const c_char,
585 listening_socket_path: *const c_char,
586 event: RegisteredEventFfi,
587 ) -> bool {
588 catch_unwind(|| {
589 if let Some(socket_path) = validate_socket_path(socket_path) {
590 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
591 if let Ok(event) = event.try_into() {
592 let request = VmRequest::UnregisterListener {
593 event,
594 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
595 };
596 vms_request(&request, &socket_path).is_ok()
597 } else {
598 false
599 }
600 } else {
601 false
602 }
603 } else {
604 false
605 }
606 })
607 .unwrap_or(false)
608 }
609
610 /// Unegisters the connected process as a listener for all events.
611 #[no_mangle]
crosvm_client_unregister_listener( socket_path: *const c_char, listening_socket_path: *const c_char, ) -> bool612 pub extern "C" fn crosvm_client_unregister_listener(
613 socket_path: *const c_char,
614 listening_socket_path: *const c_char,
615 ) -> bool {
616 catch_unwind(|| {
617 if let Some(socket_path) = validate_socket_path(socket_path) {
618 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
619 let request = VmRequest::Unregister {
620 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
621 };
622 vms_request(&request, &socket_path).is_ok()
623 } else {
624 false
625 }
626 } else {
627 false
628 }
629 })
630 .unwrap_or(false)
631 }
632