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