• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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