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 #[cfg(any(target_os = "android", target_os = "linux"))]
23 use std::time::Duration;
24
25 use libc::c_char;
26 use libc::ssize_t;
27 pub use swap::SwapStatus;
28 use vm_control::client::*;
29 use vm_control::BalloonControlCommand;
30 use vm_control::BalloonStats;
31 use vm_control::BalloonWS;
32 use vm_control::DiskControlCommand;
33 #[cfg(feature = "registered_events")]
34 use vm_control::RegisteredEvent;
35 use vm_control::UsbControlAttachedDevice;
36 use vm_control::UsbControlResult;
37 use vm_control::VmRequest;
38 use vm_control::VmResponse;
39 use vm_control::WSBucket;
40 use vm_control::USB_CONTROL_MAX_PORTS;
41
42 pub const VIRTIO_BALLOON_WS_MAX_NUM_BINS: usize = 16;
43 pub const VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS: usize = 15;
44
validate_socket_path(socket_path: *const c_char) -> Option<PathBuf>45 fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> {
46 if !socket_path.is_null() {
47 // SAFETY: just checked that `socket_path` is not null.
48 let socket_path = unsafe { CStr::from_ptr(socket_path) };
49 Some(PathBuf::from(socket_path.to_str().ok()?))
50 } else {
51 None
52 }
53 }
54
55 /// Stops the crosvm instance whose control socket is listening on `socket_path`.
56 ///
57 /// The function returns true on success or false if an error occurred.
58 ///
59 /// # Safety
60 ///
61 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
62 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
63 /// null pointers are passed.
64 #[no_mangle]
crosvm_client_stop_vm(socket_path: *const c_char) -> bool65 pub unsafe extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool {
66 catch_unwind(|| {
67 if let Some(socket_path) = validate_socket_path(socket_path) {
68 vms_request(&VmRequest::Exit, socket_path).is_ok()
69 } else {
70 false
71 }
72 })
73 .unwrap_or(false)
74 }
75
76 /// Suspends the crosvm instance whose control socket is listening on `socket_path`.
77 ///
78 /// The function returns true on success or false if an error occurred.
79 ///
80 /// # Safety
81 ///
82 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
83 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
84 /// null pointers are passed.
85 #[no_mangle]
crosvm_client_suspend_vm(socket_path: *const c_char) -> bool86 pub unsafe extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool {
87 catch_unwind(|| {
88 if let Some(socket_path) = validate_socket_path(socket_path) {
89 vms_request(&VmRequest::SuspendVcpus, socket_path).is_ok()
90 } else {
91 false
92 }
93 })
94 .unwrap_or(false)
95 }
96
97 /// Resumes the crosvm instance whose control socket is listening on `socket_path`.
98 ///
99 /// The function returns true on success or false if an error occurred.
100 ///
101 /// # Safety
102 ///
103 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
104 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
105 /// null pointers are passed.
106 #[no_mangle]
crosvm_client_resume_vm(socket_path: *const c_char) -> bool107 pub unsafe extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool {
108 catch_unwind(|| {
109 if let Some(socket_path) = validate_socket_path(socket_path) {
110 vms_request(&VmRequest::ResumeVcpus, socket_path).is_ok()
111 } else {
112 false
113 }
114 })
115 .unwrap_or(false)
116 }
117
118 /// Creates an RT vCPU for the crosvm instance whose control socket is listening on `socket_path`.
119 ///
120 /// The function returns true on success or false if an error occurred.
121 ///
122 /// # Safety
123 ///
124 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
125 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
126 /// null pointers are passed.
127 #[no_mangle]
crosvm_client_make_rt_vm(socket_path: *const c_char) -> bool128 pub unsafe extern "C" fn crosvm_client_make_rt_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::MakeRT, socket_path).is_ok()
132 } else {
133 false
134 }
135 })
136 .unwrap_or(false)
137 }
138
139 /// Adjusts the balloon size of the crosvm instance whose control socket is
140 /// listening on `socket_path`.
141 ///
142 /// The function returns true on success or false if an error occurred.
143 ///
144 /// # Safety
145 ///
146 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
147 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
148 /// null pointers are passed.
149 #[no_mangle]
crosvm_client_balloon_vms( socket_path: *const c_char, num_bytes: u64, ) -> bool150 pub unsafe extern "C" fn crosvm_client_balloon_vms(
151 socket_path: *const c_char,
152 num_bytes: u64,
153 ) -> bool {
154 catch_unwind(|| {
155 if let Some(socket_path) = validate_socket_path(socket_path) {
156 let command = BalloonControlCommand::Adjust {
157 num_bytes,
158 wait_for_success: false,
159 };
160 vms_request(&VmRequest::BalloonCommand(command), socket_path).is_ok()
161 } else {
162 false
163 }
164 })
165 .unwrap_or(false)
166 }
167
168 /// See crosvm_client_balloon_vms.
169 ///
170 /// # Safety
171 ///
172 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
173 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
174 /// null pointers are passed.
175 #[cfg(any(target_os = "android", target_os = "linux"))]
176 #[no_mangle]
crosvm_client_balloon_vms_wait_with_timeout( socket_path: *const c_char, num_bytes: u64, timeout_ms: u64, ) -> bool177 pub unsafe extern "C" fn crosvm_client_balloon_vms_wait_with_timeout(
178 socket_path: *const c_char,
179 num_bytes: u64,
180 timeout_ms: u64,
181 ) -> bool {
182 catch_unwind(|| {
183 if let Some(socket_path) = validate_socket_path(socket_path) {
184 let command = BalloonControlCommand::Adjust {
185 num_bytes,
186 wait_for_success: true,
187 };
188 let resp = handle_request_with_timeout(
189 &VmRequest::BalloonCommand(command),
190 socket_path,
191 Some(Duration::from_millis(timeout_ms)),
192 );
193 if matches!(resp, Ok(VmResponse::Ok)) {
194 return true;
195 }
196 println!("adjust failure: {:?}", resp);
197 }
198 false
199 })
200 .unwrap_or(false)
201 }
202
203 /// Enable vmm swap for crosvm instance whose control socket is listening on `socket_path`.
204 ///
205 /// The function returns true on success or false if an error occurred.
206 ///
207 /// # Safety
208 ///
209 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
210 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
211 /// null pointers are passed.
212 #[no_mangle]
crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool213 pub unsafe extern "C" fn crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool {
214 catch_unwind(|| {
215 if let Some(socket_path) = validate_socket_path(socket_path) {
216 vms_request(&VmRequest::Swap(SwapCommand::Enable), socket_path).is_ok()
217 } else {
218 false
219 }
220 })
221 .unwrap_or(false)
222 }
223
224 /// Swap out staging memory for crosvm instance whose control socket is listening
225 /// on `socket_path`.
226 ///
227 /// The function returns true on success or false if an error occurred.
228 ///
229 /// # Safety
230 ///
231 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
232 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
233 /// null pointers are passed.
234 #[no_mangle]
crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool235 pub unsafe extern "C" fn crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool {
236 catch_unwind(|| {
237 if let Some(socket_path) = validate_socket_path(socket_path) {
238 vms_request(&VmRequest::Swap(SwapCommand::SwapOut), socket_path).is_ok()
239 } else {
240 false
241 }
242 })
243 .unwrap_or(false)
244 }
245
246 /// Arguments structure for crosvm_client_swap_disable_vm2.
247 #[repr(C)]
248 pub struct SwapDisableArgs {
249 /// The path of the control socket to target.
250 socket_path: *const c_char,
251 /// Whether or not the swap file should be cleaned up in the background.
252 slow_file_cleanup: bool,
253 }
254
255 /// Disable vmm swap according to `args`.
256 ///
257 /// The function returns true on success or false if an error occurred.
258 ///
259 /// # Safety
260 ///
261 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
262 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
263 /// null pointers are passed.
264 #[no_mangle]
crosvm_client_swap_disable_vm(args: *mut SwapDisableArgs) -> bool265 pub unsafe extern "C" fn crosvm_client_swap_disable_vm(args: *mut SwapDisableArgs) -> bool {
266 catch_unwind(|| {
267 if args.is_null() {
268 return false;
269 }
270 let Some(socket_path) = validate_socket_path((*args).socket_path) else {
271 return false;
272 };
273 vms_request(
274 &VmRequest::Swap(SwapCommand::Disable {
275 slow_file_cleanup: (*args).slow_file_cleanup,
276 }),
277 socket_path,
278 )
279 .is_ok()
280 })
281 .unwrap_or(false)
282 }
283
284 /// Trim staging memory for vmm swap for crosvm instance whose control socket is listening on
285 /// `socket_path`.
286 ///
287 /// The function returns true on success or false if an error occurred.
288 ///
289 /// # Safety
290 ///
291 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
292 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
293 /// null pointers are passed.
294 #[no_mangle]
crosvm_client_swap_trim(socket_path: *const c_char) -> bool295 pub unsafe extern "C" fn crosvm_client_swap_trim(socket_path: *const c_char) -> bool {
296 catch_unwind(|| {
297 if let Some(socket_path) = validate_socket_path(socket_path) {
298 vms_request(&VmRequest::Swap(SwapCommand::Trim), socket_path).is_ok()
299 } else {
300 false
301 }
302 })
303 .unwrap_or(false)
304 }
305
306 /// Returns vmm-swap status of the crosvm instance whose control socket is listening on
307 /// `socket_path`.
308 ///
309 /// The parameters `status` is optional and will only be written to if they are non-null.
310 ///
311 /// The function returns true on success or false if an error occurred.
312 ///
313 /// # Safety
314 ///
315 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
316 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
317 /// null pointers are passed.
318 #[no_mangle]
crosvm_client_swap_status( socket_path: *const c_char, status: *mut SwapStatus, ) -> bool319 pub unsafe extern "C" fn crosvm_client_swap_status(
320 socket_path: *const c_char,
321 status: *mut SwapStatus,
322 ) -> bool {
323 catch_unwind(|| {
324 if let Some(socket_path) = validate_socket_path(socket_path) {
325 let request = &VmRequest::Swap(SwapCommand::Status);
326 if let Ok(VmResponse::SwapStatus(response)) = handle_request(request, socket_path) {
327 if !status.is_null() {
328 // SAFETY: just checked that `status` is not null.
329 unsafe {
330 *status = response;
331 }
332 }
333 true
334 } else {
335 false
336 }
337 } else {
338 false
339 }
340 })
341 .unwrap_or(false)
342 }
343
344 /// Represents an individual attached USB device.
345 #[repr(C)]
346 pub struct UsbDeviceEntry {
347 /// Internal port index used for identifying this individual device.
348 port: u8,
349 /// USB vendor ID
350 vendor_id: u16,
351 /// USB product ID
352 product_id: u16,
353 }
354
355 impl From<&UsbControlAttachedDevice> for UsbDeviceEntry {
from(other: &UsbControlAttachedDevice) -> Self356 fn from(other: &UsbControlAttachedDevice) -> Self {
357 Self {
358 port: other.port,
359 vendor_id: other.vendor_id,
360 product_id: other.product_id,
361 }
362 }
363 }
364
365 /// Simply returns the maximum possible number of USB devices
366 #[no_mangle]
crosvm_client_max_usb_devices() -> usize367 pub extern "C" fn crosvm_client_max_usb_devices() -> usize {
368 USB_CONTROL_MAX_PORTS
369 }
370
371 /// Returns all USB devices passed through the crosvm instance whose control socket is listening on
372 /// `socket_path`.
373 ///
374 /// The function returns the amount of entries written.
375 /// # Arguments
376 ///
377 /// * `socket_path` - Path to the crosvm control socket
378 /// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached
379 /// devices will be written to
380 /// * `entries_length` - Amount of entries in the array specified by `entries`
381 ///
382 /// Use the value returned by [`crosvm_client_max_usb_devices()`] to determine the size of the input
383 /// array to this function.
384 ///
385 /// # Safety
386 ///
387 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
388 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
389 /// null pointers are passed.
390 #[no_mangle]
crosvm_client_usb_list( socket_path: *const c_char, entries: *mut UsbDeviceEntry, entries_length: ssize_t, ) -> ssize_t391 pub unsafe extern "C" fn crosvm_client_usb_list(
392 socket_path: *const c_char,
393 entries: *mut UsbDeviceEntry,
394 entries_length: ssize_t,
395 ) -> ssize_t {
396 catch_unwind(|| {
397 if let Some(socket_path) = validate_socket_path(socket_path) {
398 if entries.is_null() {
399 return -1;
400 }
401 if let Ok(UsbControlResult::Devices(res)) = do_usb_list(&socket_path) {
402 let mut i = 0;
403 for entry in res.iter().filter(|x| x.valid()) {
404 if i >= entries_length {
405 break;
406 }
407 // SAFETY: checked that `entries` is not null.
408 unsafe {
409 *entries.offset(i) = entry.into();
410 i += 1;
411 }
412 }
413 i
414 } else {
415 -1
416 }
417 } else {
418 -1
419 }
420 })
421 .unwrap_or(-1)
422 }
423
424 /// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`.
425 ///
426 /// The function returns the amount of entries written.
427 /// # Arguments
428 ///
429 /// * `socket_path` - Path to the crosvm control socket
430 /// * `bus` - USB device bus ID (unused)
431 /// * `addr` - USB device address (unused)
432 /// * `vid` - USB device vendor ID (unused)
433 /// * `pid` - USB device product ID (unused)
434 /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`).
435 /// * `out_port` - (optional) internal port will be written here if provided.
436 ///
437 /// The function returns true on success or false if an error occurred.
438 ///
439 /// # Safety
440 ///
441 /// Function is unsafe due to raw pointer usage.
442 /// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should
443 /// ensure no null pointers are passed into the function.
444 ///
445 /// The safety requirements for `socket_path` and `dev_path` are the same as the ones from
446 /// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte
447 /// region.
448 #[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, ) -> bool449 pub unsafe extern "C" fn crosvm_client_usb_attach(
450 socket_path: *const c_char,
451 _bus: u8,
452 _addr: u8,
453 _vid: u16,
454 _pid: u16,
455 dev_path: *const c_char,
456 out_port: *mut u8,
457 ) -> bool {
458 catch_unwind(|| {
459 if let Some(socket_path) = validate_socket_path(socket_path) {
460 if dev_path.is_null() {
461 return false;
462 }
463 // SAFETY: just checked that `dev_path` is not null.
464 let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or(""));
465
466 if let Ok(UsbControlResult::Ok { port }) = do_usb_attach(socket_path, dev_path) {
467 if !out_port.is_null() {
468 // SAFETY: trivially safe
469 unsafe { *out_port = port };
470 }
471 true
472 } else {
473 false
474 }
475 } else {
476 false
477 }
478 })
479 .unwrap_or(false)
480 }
481
482 /// Attaches a u2f security key to crosvm instance whose control socket is listening on
483 /// `socket_path`.
484 ///
485 /// The function returns the amount of entries written.
486 /// # Arguments
487 ///
488 /// * `socket_path` - Path to the crosvm control socket
489 /// * `hidraw_path` - Path to the hidraw device of the security key (like `/dev/hidraw0`)
490 /// * `out_port` - (optional) internal port will be written here if provided.
491 ///
492 /// The function returns true on success or false if an error occurred.
493 ///
494 /// # Safety
495 ///
496 /// Function is unsafe due to raw pointer usage.
497 /// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should
498 /// ensure no null pointers are passed into the function.
499 ///
500 /// The safety requirements for `socket_path` and `hidraw_path` are the same as the ones from
501 /// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte
502 /// region.
503 #[no_mangle]
crosvm_client_security_key_attach( socket_path: *const c_char, hidraw_path: *const c_char, out_port: *mut u8, ) -> bool504 pub unsafe extern "C" fn crosvm_client_security_key_attach(
505 socket_path: *const c_char,
506 hidraw_path: *const c_char,
507 out_port: *mut u8,
508 ) -> bool {
509 catch_unwind(|| {
510 if let Some(socket_path) = validate_socket_path(socket_path) {
511 if hidraw_path.is_null() {
512 return false;
513 }
514 let hidraw_path = Path::new(
515 // SAFETY: just checked that `hidraw_path` is not null.
516 unsafe { CStr::from_ptr(hidraw_path) }
517 .to_str()
518 .unwrap_or(""),
519 );
520
521 if let Ok(UsbControlResult::Ok { port }) =
522 do_security_key_attach(socket_path, hidraw_path)
523 {
524 if !out_port.is_null() {
525 // SAFETY: trivially safe
526 unsafe { *out_port = port };
527 }
528 true
529 } else {
530 false
531 }
532 } else {
533 false
534 }
535 })
536 .unwrap_or(false)
537 }
538
539 /// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`.
540 /// `port` determines device to be detached.
541 ///
542 /// The function returns true on success or false if an error occurred.
543 ///
544 /// # Safety
545 ///
546 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
547 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
548 /// null pointers are passed.
549 #[no_mangle]
crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool550 pub unsafe extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool {
551 catch_unwind(|| {
552 if let Some(socket_path) = validate_socket_path(socket_path) {
553 do_usb_detach(socket_path, port).is_ok()
554 } else {
555 false
556 }
557 })
558 .unwrap_or(false)
559 }
560
561 /// Attaches a net tap device to the crosvm instance with control socket at `socket_path`.
562 ///
563 /// # Arguments
564 ///
565 /// * `socket_path` - Path to the crosvm control socket
566 /// * `tap_name` - Name of the tap device
567 /// * `out_bus_num` - guest bus number will be written here
568 ///
569 /// The function returns true on success, false on failure.
570 ///
571 /// # Safety
572 ///
573 /// Function is unsafe due to raw pointer usage - socket_path and tap_name are assumed to point to a
574 /// null-terminated CStr. Function checks that the pointers are not null, but caller need to check
575 /// the validity of the pointer. out_bus_num is assumed to point to a u8 integer.
576 #[no_mangle]
crosvm_client_net_tap_attach( socket_path: *const c_char, tap_name: *const c_char, out_bus_num: *mut u8, ) -> bool577 pub unsafe extern "C" fn crosvm_client_net_tap_attach(
578 socket_path: *const c_char,
579 tap_name: *const c_char,
580 out_bus_num: *mut u8,
581 ) -> bool {
582 catch_unwind(|| {
583 if let Some(socket_path) = validate_socket_path(socket_path) {
584 if tap_name.is_null() || out_bus_num.is_null() {
585 return false;
586 }
587 // SAFETY: just checked that `tap_name` is not null. Function caller guarantees it
588 // points to a valid CStr.
589 let tap_name = unsafe { CStr::from_ptr(tap_name) }.to_str().unwrap_or("");
590
591 match do_net_add(tap_name, socket_path) {
592 Ok(bus_num) => {
593 // SAFETY: checked out_bus_num is not null. Function caller guarantees
594 // validity of pointer.
595 unsafe { *out_bus_num = bus_num };
596 true
597 }
598 Err(_e) => false,
599 }
600 } else {
601 false
602 }
603 })
604 .unwrap_or(false)
605 }
606
607 /// Detaches a hotplugged tap device from the crosvm instance with control socket at `socket_path`.
608 ///
609 /// # Arguments
610 ///
611 /// * `socket_path` - Path to the crosvm control socket
612 /// * `bus_num` - Bus number of the tap device to be removed.
613 ///
614 /// The function returns true on success, and false on failure.
615 ///
616 /// # Safety
617 ///
618 /// Function is unsafe due to raw pointer usage - socket_path is assumed to point to a
619 /// null-terminated Cstr. Function checks that the pointers are not null, but caller need to check
620 /// the validity of the pointer.
621 #[no_mangle]
crosvm_client_net_tap_detach( socket_path: *const c_char, bus_num: u8, ) -> bool622 pub unsafe extern "C" fn crosvm_client_net_tap_detach(
623 socket_path: *const c_char,
624 bus_num: u8,
625 ) -> bool {
626 catch_unwind(|| {
627 if let Some(socket_path) = validate_socket_path(socket_path) {
628 match do_net_remove(bus_num, socket_path) {
629 Ok(()) => true,
630 Err(_e) => false,
631 }
632 } else {
633 false
634 }
635 })
636 .unwrap_or(false)
637 }
638
639 /// Modifies the battery status of crosvm instance whose control socket is listening on
640 /// `socket_path`.
641 ///
642 /// The function returns true on success or false if an error occurred.
643 ///
644 /// # Safety
645 ///
646 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
647 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
648 /// null pointers are passed.
649 #[no_mangle]
crosvm_client_modify_battery( socket_path: *const c_char, battery_type: *const c_char, property: *const c_char, target: *const c_char, ) -> bool650 pub unsafe extern "C" fn crosvm_client_modify_battery(
651 socket_path: *const c_char,
652 battery_type: *const c_char,
653 property: *const c_char,
654 target: *const c_char,
655 ) -> bool {
656 catch_unwind(|| {
657 if let Some(socket_path) = validate_socket_path(socket_path) {
658 if battery_type.is_null() || property.is_null() || target.is_null() {
659 return false;
660 }
661 // SAFETY: trivially safe
662 let battery_type = unsafe { CStr::from_ptr(battery_type) };
663 // SAFETY: trivially safe
664 let property = unsafe { CStr::from_ptr(property) };
665 // SAFETY: trivially safe
666 let target = unsafe { CStr::from_ptr(target) };
667
668 do_modify_battery(
669 socket_path,
670 battery_type.to_str().unwrap(),
671 property.to_str().unwrap(),
672 target.to_str().unwrap(),
673 )
674 .is_ok()
675 } else {
676 false
677 }
678 })
679 .unwrap_or(false)
680 }
681
682 /// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`.
683 ///
684 /// The function returns true on success or false if an error occurred.
685 ///
686 /// # Safety
687 ///
688 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
689 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
690 /// null pointers are passed.
691 #[no_mangle]
crosvm_client_resize_disk( socket_path: *const c_char, disk_index: u64, new_size: u64, ) -> bool692 pub unsafe extern "C" fn crosvm_client_resize_disk(
693 socket_path: *const c_char,
694 disk_index: u64,
695 new_size: u64,
696 ) -> bool {
697 catch_unwind(|| {
698 if let Some(socket_path) = validate_socket_path(socket_path) {
699 if let Ok(disk_index) = usize::try_from(disk_index) {
700 let request = VmRequest::DiskCommand {
701 disk_index,
702 command: DiskControlCommand::Resize { new_size },
703 };
704 vms_request(&request, socket_path).is_ok()
705 } else {
706 false
707 }
708 } else {
709 false
710 }
711 })
712 .unwrap_or(false)
713 }
714
715 /// Similar to internally used `BalloonStats` but using `i64` instead of
716 /// `Option<u64>`. `None` (or values bigger than `i64::max`) will be encoded as -1.
717 #[repr(C)]
718 pub struct BalloonStatsFfi {
719 swap_in: i64,
720 swap_out: i64,
721 major_faults: i64,
722 minor_faults: i64,
723 free_memory: i64,
724 total_memory: i64,
725 available_memory: i64,
726 disk_caches: i64,
727 hugetlb_allocations: i64,
728 hugetlb_failures: i64,
729 shared_memory: i64,
730 unevictable_memory: i64,
731 }
732
733 impl From<&BalloonStats> for BalloonStatsFfi {
from(other: &BalloonStats) -> Self734 fn from(other: &BalloonStats) -> Self {
735 let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
736 Self {
737 swap_in: convert(other.swap_in),
738 swap_out: convert(other.swap_out),
739 major_faults: convert(other.major_faults),
740 minor_faults: convert(other.minor_faults),
741 free_memory: convert(other.free_memory),
742 total_memory: convert(other.total_memory),
743 available_memory: convert(other.available_memory),
744 disk_caches: convert(other.disk_caches),
745 hugetlb_allocations: convert(other.hugetlb_allocations),
746 hugetlb_failures: convert(other.hugetlb_failures),
747 shared_memory: convert(other.shared_memory),
748 unevictable_memory: convert(other.unevictable_memory),
749 }
750 }
751 }
752
753 /// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`.
754 ///
755 /// The parameters `stats` and `actual` are optional and will only be written to if they are
756 /// non-null.
757 ///
758 /// The function returns true on success or false if an error occurred.
759 ///
760 /// # Note
761 ///
762 /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`.
763 ///
764 /// # Safety
765 ///
766 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
767 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
768 /// null pointers are passed.
769 #[no_mangle]
crosvm_client_balloon_stats( socket_path: *const c_char, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool770 pub unsafe extern "C" fn crosvm_client_balloon_stats(
771 socket_path: *const c_char,
772 stats: *mut BalloonStatsFfi,
773 actual: *mut u64,
774 ) -> bool {
775 crosvm_client_balloon_stats_impl(
776 socket_path,
777 #[cfg(any(target_os = "android", target_os = "linux"))]
778 None,
779 stats,
780 actual,
781 )
782 }
783
784 /// See crosvm_client_balloon_stats.
785 ///
786 /// # Safety
787 ///
788 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
789 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
790 /// null pointers are passed.
791 #[cfg(any(target_os = "android", target_os = "linux"))]
792 #[no_mangle]
crosvm_client_balloon_stats_with_timeout( socket_path: *const c_char, timeout_ms: u64, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool793 pub unsafe extern "C" fn crosvm_client_balloon_stats_with_timeout(
794 socket_path: *const c_char,
795 timeout_ms: u64,
796 stats: *mut BalloonStatsFfi,
797 actual: *mut u64,
798 ) -> bool {
799 crosvm_client_balloon_stats_impl(
800 socket_path,
801 Some(Duration::from_millis(timeout_ms)),
802 stats,
803 actual,
804 )
805 }
806
crosvm_client_balloon_stats_impl( socket_path: *const c_char, #[cfg(any(target_os = "android", target_os = "linux"))] timeout_ms: Option<Duration>, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool807 fn crosvm_client_balloon_stats_impl(
808 socket_path: *const c_char,
809 #[cfg(any(target_os = "android", target_os = "linux"))] timeout_ms: Option<Duration>,
810 stats: *mut BalloonStatsFfi,
811 actual: *mut u64,
812 ) -> bool {
813 catch_unwind(|| {
814 if let Some(socket_path) = validate_socket_path(socket_path) {
815 let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
816 #[cfg(not(unix))]
817 let resp = handle_request(request, socket_path);
818 #[cfg(any(target_os = "android", target_os = "linux"))]
819 let resp = handle_request_with_timeout(request, socket_path, timeout_ms);
820 if let Ok(VmResponse::BalloonStats {
821 stats: ref balloon_stats,
822 balloon_actual,
823 }) = resp
824 {
825 if !stats.is_null() {
826 // SAFETY: just checked that `stats` is not null.
827 unsafe {
828 *stats = balloon_stats.into();
829 }
830 }
831
832 if !actual.is_null() {
833 // SAFETY: just checked that `actual` is not null.
834 unsafe {
835 *actual = balloon_actual;
836 }
837 }
838 true
839 } else {
840 false
841 }
842 } else {
843 false
844 }
845 })
846 .unwrap_or(false)
847 }
848
849 /// Externally exposed variant of BalloonWS/WSBucket, used for FFI.
850 #[derive(Clone, Copy, Debug)]
851 #[repr(C)]
852 pub struct WorkingSetBucketFfi {
853 age: u64,
854 bytes: [u64; 2],
855 }
856
857 impl WorkingSetBucketFfi {
new() -> Self858 fn new() -> Self {
859 Self {
860 age: 0,
861 bytes: [0, 0],
862 }
863 }
864 }
865
866 impl From<WSBucket> for WorkingSetBucketFfi {
from(other: WSBucket) -> Self867 fn from(other: WSBucket) -> Self {
868 Self {
869 age: other.age,
870 bytes: other.bytes,
871 }
872 }
873 }
874
875 #[repr(C)]
876 #[derive(Debug)]
877 pub struct BalloonWSFfi {
878 ws: [WorkingSetBucketFfi; VIRTIO_BALLOON_WS_MAX_NUM_BINS],
879 num_bins: u8,
880 _reserved: [u8; 7],
881 }
882
883 impl TryFrom<&BalloonWS> for BalloonWSFfi {
884 type Error = &'static str;
885
try_from(value: &BalloonWS) -> Result<Self, Self::Error>886 fn try_from(value: &BalloonWS) -> Result<Self, Self::Error> {
887 if value.ws.len() > VIRTIO_BALLOON_WS_MAX_NUM_BINS {
888 return Err("too many WS buckets in source object.");
889 }
890
891 let mut ffi = Self {
892 ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS],
893 num_bins: value.ws.len() as u8,
894 ..Default::default()
895 };
896 for (ffi_ws, other_ws) in ffi.ws.iter_mut().zip(value.ws.iter()) {
897 *ffi_ws = (*other_ws).into();
898 }
899 Ok(ffi)
900 }
901 }
902
903 impl BalloonWSFfi {
new() -> Self904 pub fn new() -> Self {
905 Self {
906 ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS],
907 num_bins: 0,
908 _reserved: [0; 7],
909 }
910 }
911 }
912
913 impl Default for BalloonWSFfi {
default() -> Self914 fn default() -> Self {
915 Self::new()
916 }
917 }
918
919 #[repr(C)]
920 pub struct BalloonWSRConfigFfi {
921 intervals: [u64; VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS],
922 num_intervals: u8,
923 _reserved: [u8; 7],
924 refresh_threshold: u64,
925 report_threshold: u64,
926 }
927
928 /// Returns balloon working set of the crosvm instance whose control socket is listening on
929 /// socket_path.
930 ///
931 /// The function returns true on success or false if an error occurred.
932 ///
933 /// # Safety
934 ///
935 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
936 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
937 /// null pointers are passed.
938 #[no_mangle]
crosvm_client_balloon_working_set( socket_path: *const c_char, ws: *mut BalloonWSFfi, actual: *mut u64, ) -> bool939 pub unsafe extern "C" fn crosvm_client_balloon_working_set(
940 socket_path: *const c_char,
941 ws: *mut BalloonWSFfi,
942 actual: *mut u64,
943 ) -> bool {
944 catch_unwind(|| {
945 if let Some(socket_path) = validate_socket_path(socket_path) {
946 let request = &VmRequest::BalloonCommand(BalloonControlCommand::WorkingSet);
947 if let Ok(VmResponse::BalloonWS {
948 ws: ref balloon_ws,
949 balloon_actual,
950 }) = handle_request(request, socket_path)
951 {
952 if !ws.is_null() {
953 // SAFETY: just checked that `ws` is not null.
954 unsafe {
955 *ws = match balloon_ws.try_into() {
956 Ok(result) => result,
957 Err(_) => return false,
958 };
959 }
960 }
961
962 if !actual.is_null() {
963 // SAFETY: just checked that `actual` is not null.
964 unsafe {
965 *actual = balloon_actual;
966 }
967 }
968 true
969 } else {
970 false
971 }
972 } else {
973 false
974 }
975 })
976 .unwrap_or(false)
977 }
978
979 /// Publically exposed version of RegisteredEvent enum, implemented as an
980 /// integral newtype for FFI safety.
981 #[cfg(feature = "registered_events")]
982 #[repr(C)]
983 #[derive(Copy, Clone, PartialEq, Eq)]
984 pub struct RegisteredEventFfi(u32);
985
986 #[cfg(feature = "registered_events")]
987 pub const REGISTERED_EVENT_VIRTIO_BALLOON_WS_REPORT: RegisteredEventFfi = RegisteredEventFfi(0);
988 #[cfg(feature = "registered_events")]
989 pub const REGISTERED_EVENT_VIRTIO_BALLOON_RESIZE: RegisteredEventFfi = RegisteredEventFfi(1);
990 #[cfg(feature = "registered_events")]
991 pub const REGISTERED_EVENT_VIRTIO_BALLOON_OOM_DEFLATION: RegisteredEventFfi = RegisteredEventFfi(2);
992
993 #[cfg(feature = "registered_events")]
994 impl TryFrom<RegisteredEventFfi> for RegisteredEvent {
995 type Error = &'static str;
996
try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error>997 fn try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error> {
998 match value.0 {
999 0 => Ok(RegisteredEvent::VirtioBalloonWsReport),
1000 1 => Ok(RegisteredEvent::VirtioBalloonResize),
1001 2 => Ok(RegisteredEvent::VirtioBalloonOOMDeflation),
1002 _ => Err("RegisteredEventFFi outside of known RegisteredEvent enum range"),
1003 }
1004 }
1005 }
1006
1007 /// Registers the connected process as a listener for `event`.
1008 ///
1009 /// The function returns true on success or false if an error occurred.
1010 ///
1011 /// # Safety
1012 ///
1013 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
1014 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
1015 /// null pointers are passed.
1016 #[cfg(feature = "registered_events")]
1017 #[no_mangle]
crosvm_client_register_events_listener( socket_path: *const c_char, listening_socket_path: *const c_char, event: RegisteredEventFfi, ) -> bool1018 pub unsafe extern "C" fn crosvm_client_register_events_listener(
1019 socket_path: *const c_char,
1020 listening_socket_path: *const c_char,
1021 event: RegisteredEventFfi,
1022 ) -> bool {
1023 catch_unwind(|| {
1024 if let Some(socket_path) = validate_socket_path(socket_path) {
1025 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1026 if let Ok(event) = event.try_into() {
1027 let request = VmRequest::RegisterListener {
1028 event,
1029 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1030 };
1031 vms_request(&request, socket_path).is_ok()
1032 } else {
1033 false
1034 }
1035 } else {
1036 false
1037 }
1038 } else {
1039 false
1040 }
1041 })
1042 .unwrap_or(false)
1043 }
1044
1045 /// Unegisters the connected process as a listener for `event`.
1046 ///
1047 /// The function returns true on success or false if an error occurred.
1048 ///
1049 /// # Safety
1050 ///
1051 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
1052 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
1053 /// null pointers are passed.
1054 #[cfg(feature = "registered_events")]
1055 #[no_mangle]
crosvm_client_unregister_events_listener( socket_path: *const c_char, listening_socket_path: *const c_char, event: RegisteredEventFfi, ) -> bool1056 pub unsafe extern "C" fn crosvm_client_unregister_events_listener(
1057 socket_path: *const c_char,
1058 listening_socket_path: *const c_char,
1059 event: RegisteredEventFfi,
1060 ) -> bool {
1061 catch_unwind(|| {
1062 if let Some(socket_path) = validate_socket_path(socket_path) {
1063 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1064 if let Ok(event) = event.try_into() {
1065 let request = VmRequest::UnregisterListener {
1066 event,
1067 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1068 };
1069 vms_request(&request, socket_path).is_ok()
1070 } else {
1071 false
1072 }
1073 } else {
1074 false
1075 }
1076 } else {
1077 false
1078 }
1079 })
1080 .unwrap_or(false)
1081 }
1082
1083 /// Unegisters the connected process as a listener for all events.
1084 ///
1085 /// The function returns true on success or false if an error occurred.
1086 ///
1087 /// # Safety
1088 ///
1089 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
1090 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
1091 /// null pointers are passed.
1092 #[cfg(feature = "registered_events")]
1093 #[no_mangle]
crosvm_client_unregister_listener( socket_path: *const c_char, listening_socket_path: *const c_char, ) -> bool1094 pub unsafe extern "C" fn crosvm_client_unregister_listener(
1095 socket_path: *const c_char,
1096 listening_socket_path: *const c_char,
1097 ) -> bool {
1098 catch_unwind(|| {
1099 if let Some(socket_path) = validate_socket_path(socket_path) {
1100 if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1101 let request = VmRequest::Unregister {
1102 socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1103 };
1104 vms_request(&request, socket_path).is_ok()
1105 } else {
1106 false
1107 }
1108 } else {
1109 false
1110 }
1111 })
1112 .unwrap_or(false)
1113 }
1114
1115 /// Set Working Set Reporting config in guest.
1116 ///
1117 /// The function returns true on success or false if an error occurred.
1118 ///
1119 /// # Safety
1120 ///
1121 /// Function is unsafe due to raw pointer usage - a null pointer could be passed in. Usage of
1122 /// !raw_pointer.is_null() checks should prevent unsafe behavior but the caller should ensure no
1123 /// null pointers are passed.
1124 #[no_mangle]
crosvm_client_balloon_wsr_config( socket_path: *const c_char, config: *const BalloonWSRConfigFfi, ) -> bool1125 pub unsafe extern "C" fn crosvm_client_balloon_wsr_config(
1126 socket_path: *const c_char,
1127 config: *const BalloonWSRConfigFfi,
1128 ) -> bool {
1129 catch_unwind(|| {
1130 if let Some(socket_path) = validate_socket_path(socket_path) {
1131 if !config.is_null() {
1132 // SAFETY: just checked that `config` is not null.
1133 unsafe {
1134 if (*config).num_intervals > VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS as u8 {
1135 return false;
1136 }
1137 let mut actual_bins = vec![];
1138 for idx in 0..(*config).num_intervals {
1139 actual_bins.push((*config).intervals[idx as usize]);
1140 }
1141 let refresh_threshold = match u32::try_from((*config).refresh_threshold) {
1142 Ok(r_t) => r_t,
1143 Err(_) => return false,
1144 };
1145 let report_threshold = match u32::try_from((*config).report_threshold) {
1146 Ok(r_p) => r_p,
1147 Err(_) => return false,
1148 };
1149 let request =
1150 VmRequest::BalloonCommand(BalloonControlCommand::WorkingSetConfig {
1151 bins: actual_bins
1152 .iter()
1153 .map(|&b| u32::try_from(b).unwrap())
1154 .collect(),
1155 refresh_threshold,
1156 report_threshold,
1157 });
1158 vms_request(&request, socket_path).is_ok()
1159 }
1160 } else {
1161 false
1162 }
1163 } else {
1164 false
1165 }
1166 })
1167 .unwrap_or(false)
1168 }
1169