Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/crosvm_control/src/lib.rs
5392 views
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 base::descriptor::IntoRawDescriptor;
34
use base::FromRawDescriptor;
35
use base::SafeDescriptor;
36
use libc::c_char;
37
use libc::c_int;
38
use libc::c_void;
39
use libc::ssize_t;
40
pub use swap::SwapStatus;
41
#[cfg(feature = "gpu")]
42
use vm_control::client::do_gpu_display_add;
43
#[cfg(feature = "gpu")]
44
use vm_control::client::do_gpu_display_list;
45
#[cfg(feature = "gpu")]
46
use vm_control::client::do_gpu_display_remove;
47
use vm_control::client::do_modify_battery;
48
use vm_control::client::do_net_add;
49
use vm_control::client::do_net_remove;
50
use vm_control::client::do_security_key_attach;
51
use vm_control::client::do_snd_mute_all;
52
use vm_control::client::do_usb_attach;
53
use vm_control::client::do_usb_detach;
54
use vm_control::client::do_usb_list;
55
use vm_control::client::handle_request;
56
use vm_control::client::handle_request_with_timeout;
57
use vm_control::client::vms_request;
58
#[cfg(feature = "gpu")]
59
use vm_control::gpu::DisplayMode;
60
#[cfg(feature = "gpu")]
61
use vm_control::gpu::DisplayParameters;
62
#[cfg(feature = "gpu")]
63
use vm_control::gpu::GpuControlResult;
64
use vm_control::BalloonControlCommand;
65
use vm_control::BatProperty;
66
use vm_control::DiskControlCommand;
67
use vm_control::HypervisorKind;
68
use vm_control::RegisteredEvent;
69
use vm_control::SwapCommand;
70
use vm_control::UsbControlAttachedDevice;
71
use vm_control::UsbControlResult;
72
use vm_control::VmRequest;
73
use vm_control::VmResponse;
74
use vm_control::USB_CONTROL_MAX_PORTS;
75
76
#[repr(C)]
77
#[derive(Clone, zerocopy::FromZeros)]
78
pub struct CrosvmDisplayEntry {
79
pub id: u32,
80
pub width: u32,
81
pub height: u32,
82
pub refresh_rate: u32,
83
pub horizontal_dpi: u32,
84
pub vertical_dpi: u32,
85
}
86
87
// Must match `VIRTIO_GPU_MAX_SCANOUTS` in `devices/src/virtio/gpu/protocol.rs`
88
pub const CROSVM_MAX_DISPLAYS: usize = 16;
89
90
/// Simply returns the maximum possible number of GPU displays
91
#[no_mangle]
92
pub extern "C" fn crosvm_client_max_gpu_displays() -> usize {
93
CROSVM_MAX_DISPLAYS
94
}
95
96
/// Adds a new display to the crosvm instance whose control socket is listening on `socket_path`.
97
///
98
/// The function returns true on success or false if an error occurred.
99
///
100
/// # Safety
101
///
102
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
103
/// C string that is valid for reads and not modified for the duration of the call.
104
#[no_mangle]
105
pub unsafe extern "C" fn crosvm_client_gpu_display_add(
106
socket_path: *const c_char,
107
width: u32,
108
height: u32,
109
refresh_rate: u32,
110
horizontal_dpi: u32,
111
vertical_dpi: u32,
112
) -> bool {
113
catch_unwind(|| {
114
#[cfg(feature = "gpu")]
115
{
116
let Some(socket_path) = validate_socket_path(socket_path) else {
117
return false;
118
};
119
let mode = DisplayMode::Windowed(width, height);
120
let params = DisplayParameters::new(
121
mode,
122
false, // hidden
123
refresh_rate,
124
horizontal_dpi,
125
vertical_dpi,
126
);
127
let Ok(result) = do_gpu_display_add(socket_path, vec![params]) else {
128
return false;
129
};
130
matches!(result, GpuControlResult::DisplaysUpdated)
131
}
132
#[cfg(not(feature = "gpu"))]
133
{
134
let _ = (
135
socket_path,
136
width,
137
height,
138
refresh_rate,
139
horizontal_dpi,
140
vertical_dpi,
141
);
142
false
143
}
144
})
145
.unwrap_or(false)
146
}
147
148
/// Returns all displays attached to the crosvm instance whose control socket is listening on
149
/// `socket_path`.
150
///
151
/// The function returns the amount of entries written.
152
/// # Arguments
153
///
154
/// * `socket_path` - Path to the crosvm control socket
155
/// * `entries` - Pointer to an array of `CrosvmDisplayEntry` where the details about the attached
156
/// displays will be written to
157
/// * `entries_length` - Amount of entries in the array specified by `entries`
158
///
159
/// # Safety
160
///
161
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
162
/// string that is valid and for reads and not modified for the duration of the call. `entries`
163
/// should be a valid pointer to an array of `CrosvmDisplayEntry` valid for writes that contains at
164
/// least `entries_length` elements and is not externally modified for the duration of this call.
165
#[no_mangle]
166
pub unsafe extern "C" fn crosvm_client_gpu_display_list(
167
socket_path: *const c_char,
168
entries: *mut CrosvmDisplayEntry,
169
entries_length: ssize_t,
170
) -> ssize_t {
171
catch_unwind(|| {
172
#[cfg(feature = "gpu")]
173
{
174
if entries.is_null() {
175
return -1;
176
}
177
let Some(socket_path) = validate_socket_path(socket_path) else {
178
return -1;
179
};
180
let Ok(GpuControlResult::DisplayList { displays }) = do_gpu_display_list(socket_path)
181
else {
182
return -1;
183
};
184
185
let limit = usize::try_from(entries_length).unwrap_or(0);
186
if limit == 0 {
187
return 0;
188
}
189
let cnt = limit.min(displays.len());
190
// SAFETY: checked that `entries` is not null and `limit` is less than or equal to
191
// `entries_length`.
192
let entries_slice = unsafe {
193
std::slice::from_raw_parts_mut(entries as *mut CrosvmDisplayEntry, limit)
194
};
195
for (entry, (id, params)) in entries_slice.iter_mut().zip(displays) {
196
let (width, height) = params.get_window_size();
197
*entry = CrosvmDisplayEntry {
198
id,
199
width,
200
height,
201
refresh_rate: params.refresh_rate,
202
horizontal_dpi: params.horizontal_dpi(),
203
vertical_dpi: params.vertical_dpi(),
204
};
205
}
206
cnt as ssize_t
207
}
208
#[cfg(not(feature = "gpu"))]
209
{
210
let _ = (socket_path, entries, entries_length);
211
-1
212
}
213
})
214
.unwrap_or(-1)
215
}
216
217
/// Removes a display from the crosvm instance whose control socket is listening on `socket_path`.
218
///
219
/// The function returns true on success or false if an error occurred.
220
///
221
/// # Safety
222
///
223
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
224
/// C string that is valid for reads and not modified for the duration of the call.
225
#[no_mangle]
226
pub unsafe extern "C" fn crosvm_client_gpu_display_remove(
227
socket_path: *const c_char,
228
display_id: u32,
229
) -> bool {
230
catch_unwind(|| {
231
#[cfg(feature = "gpu")]
232
{
233
let Some(socket_path) = validate_socket_path(socket_path) else {
234
return false;
235
};
236
let Ok(result) = do_gpu_display_remove(socket_path, vec![display_id]) else {
237
return false;
238
};
239
matches!(result, GpuControlResult::DisplaysUpdated)
240
}
241
#[cfg(not(feature = "gpu"))]
242
{
243
let _ = (socket_path, display_id);
244
false
245
}
246
})
247
.unwrap_or(false)
248
}
249
250
pub const VIRTIO_BALLOON_WS_MAX_NUM_BINS: usize = 16;
251
pub const VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS: usize = 15;
252
253
/// # Safety
254
///
255
/// This function is safe when the caller ensures the socket_path raw pointer can be safely passed
256
/// to `CStr::from_ptr()`.
257
unsafe fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> {
258
if !socket_path.is_null() {
259
let socket_path = CStr::from_ptr(socket_path);
260
Some(PathBuf::from(socket_path.to_str().ok()?))
261
} else {
262
None
263
}
264
}
265
266
/// Stops the crosvm instance whose control socket is listening on `socket_path`.
267
///
268
/// The function returns true on success or false if an error occurred.
269
///
270
/// # Safety
271
///
272
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
273
/// C string that is valid and not modified for the duration of the call.
274
#[no_mangle]
275
pub unsafe extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool {
276
catch_unwind(|| {
277
if let Some(socket_path) = validate_socket_path(socket_path) {
278
vms_request(&VmRequest::Exit, socket_path).is_ok()
279
} else {
280
false
281
}
282
})
283
.unwrap_or(false)
284
}
285
286
/// Suspends the crosvm instance whose control socket is listening on `socket_path`.
287
///
288
/// The function returns true on success or false if an error occurred.
289
///
290
/// # Safety
291
///
292
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
293
/// C string that is valid and not modified for the duration of the call.
294
#[no_mangle]
295
pub unsafe extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool {
296
catch_unwind(|| {
297
if let Some(socket_path) = validate_socket_path(socket_path) {
298
vms_request(&VmRequest::SuspendVcpus, socket_path).is_ok()
299
} else {
300
false
301
}
302
})
303
.unwrap_or(false)
304
}
305
306
/// Resumes the crosvm instance whose control socket is listening on `socket_path`.
307
///
308
/// Note: this function just resumes vcpus of the vm. If you need to perform a full resume, call
309
/// crosvm_client_resume_vm_full.
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 - `socket_path` should be a non-null pointer to a
316
/// C string that is valid for reads and not modified for the duration of the call.
317
#[no_mangle]
318
pub unsafe extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool {
319
catch_unwind(|| {
320
if let Some(socket_path) = validate_socket_path(socket_path) {
321
vms_request(&VmRequest::ResumeVcpus, socket_path).is_ok()
322
} else {
323
false
324
}
325
})
326
.unwrap_or(false)
327
}
328
329
/// Resumes the crosvm instance whose control socket is listening on `socket_path`.
330
///
331
/// Note: unlike crosvm_client_resume_vm, this function resumes both vcpus and devices.
332
///
333
/// The function returns true on success or false if an error occurred.
334
///
335
/// # Safety
336
///
337
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
338
/// C string that is valid for reads and not modified for the duration of the call.
339
#[no_mangle]
340
pub unsafe extern "C" fn crosvm_client_resume_vm_full(socket_path: *const c_char) -> bool {
341
catch_unwind(|| {
342
if let Some(socket_path) = validate_socket_path(socket_path) {
343
vms_request(&VmRequest::ResumeVm, socket_path).is_ok()
344
} else {
345
false
346
}
347
})
348
.unwrap_or(false)
349
}
350
351
/// Creates an RT vCPU for the crosvm instance whose control socket is listening on `socket_path`.
352
///
353
/// The function returns true on success or false if an error occurred.
354
///
355
/// # Safety
356
///
357
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
358
/// C string that is valid for reads and not modified for the duration of the call.
359
#[no_mangle]
360
pub unsafe extern "C" fn crosvm_client_make_rt_vm(socket_path: *const c_char) -> bool {
361
catch_unwind(|| {
362
if let Some(socket_path) = validate_socket_path(socket_path) {
363
vms_request(&VmRequest::MakeRT, socket_path).is_ok()
364
} else {
365
false
366
}
367
})
368
.unwrap_or(false)
369
}
370
371
/// Adjusts the balloon size of the crosvm instance whose control socket is
372
/// listening on `socket_path`.
373
///
374
/// The function returns true on success or false if an error occurred.
375
///
376
/// # Safety
377
///
378
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
379
/// C string that is valid for reads and not modified for the duration of the call.
380
#[no_mangle]
381
pub unsafe extern "C" fn crosvm_client_balloon_vms(
382
socket_path: *const c_char,
383
num_bytes: u64,
384
) -> bool {
385
catch_unwind(|| {
386
if let Some(socket_path) = validate_socket_path(socket_path) {
387
let command = BalloonControlCommand::Adjust {
388
num_bytes,
389
wait_for_success: false,
390
};
391
vms_request(&VmRequest::BalloonCommand(command), socket_path).is_ok()
392
} else {
393
false
394
}
395
})
396
.unwrap_or(false)
397
}
398
399
/// See crosvm_client_balloon_vms.
400
///
401
/// # Safety
402
///
403
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
404
/// C string that is valid for reads and not modified for the duration of the call.
405
#[no_mangle]
406
pub unsafe extern "C" fn crosvm_client_balloon_vms_wait_with_timeout(
407
socket_path: *const c_char,
408
num_bytes: u64,
409
timeout_ms: u64,
410
) -> bool {
411
catch_unwind(|| {
412
if let Some(socket_path) = validate_socket_path(socket_path) {
413
let command = BalloonControlCommand::Adjust {
414
num_bytes,
415
wait_for_success: true,
416
};
417
let resp = handle_request_with_timeout(
418
&VmRequest::BalloonCommand(command),
419
socket_path,
420
Some(Duration::from_millis(timeout_ms)),
421
);
422
if matches!(resp, Ok(VmResponse::Ok)) {
423
return true;
424
}
425
println!("adjust failure: {resp:?}");
426
}
427
false
428
})
429
.unwrap_or(false)
430
}
431
432
/// Mute or unmute all snd devices of the crosvm instance whose control socket is
433
/// listening on `socket_path`.
434
///
435
/// The function returns true on success or false if an error occurred.
436
///
437
/// # Safety
438
///
439
/// The caller will ensure the raw pointers in arguments passed in can be safely used by
440
/// `CStr::from_ptr()`
441
#[no_mangle]
442
pub unsafe extern "C" fn crosvm_client_snd_mute_all(
443
socket_path: *const c_char,
444
muted: bool,
445
) -> bool {
446
catch_unwind(|| {
447
if let Some(socket_path) = validate_socket_path(socket_path) {
448
do_snd_mute_all(socket_path, muted).is_ok()
449
} else {
450
false
451
}
452
})
453
.unwrap_or(false)
454
}
455
456
/// Enable vmm swap for crosvm instance whose control socket is listening on `socket_path`.
457
///
458
/// The function returns true on success or false if an error occurred.
459
///
460
/// # Safety
461
///
462
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
463
/// C string that is valid for reads and not modified for the duration of the call.
464
#[no_mangle]
465
pub unsafe extern "C" fn crosvm_client_swap_enable_vm(socket_path: *const c_char) -> bool {
466
catch_unwind(|| {
467
if let Some(socket_path) = validate_socket_path(socket_path) {
468
vms_request(&VmRequest::Swap(SwapCommand::Enable), socket_path).is_ok()
469
} else {
470
false
471
}
472
})
473
.unwrap_or(false)
474
}
475
476
/// Swap out staging memory for crosvm instance whose control socket is listening
477
/// on `socket_path`.
478
///
479
/// The function returns true on success or false if an error occurred.
480
///
481
/// # Safety
482
///
483
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
484
/// C string that is valid for reads and not modified for the duration of the call.
485
#[no_mangle]
486
pub unsafe extern "C" fn crosvm_client_swap_swapout_vm(socket_path: *const c_char) -> bool {
487
catch_unwind(|| {
488
if let Some(socket_path) = validate_socket_path(socket_path) {
489
vms_request(&VmRequest::Swap(SwapCommand::SwapOut), socket_path).is_ok()
490
} else {
491
false
492
}
493
})
494
.unwrap_or(false)
495
}
496
497
/// Arguments structure for crosvm_client_swap_disable_vm2.
498
#[repr(C)]
499
pub struct SwapDisableArgs {
500
/// The path of the control socket to target.
501
pub socket_path: *const c_char,
502
/// Whether or not the swap file should be cleaned up in the background.
503
pub slow_file_cleanup: bool,
504
}
505
506
/// Disable vmm swap according to `args`.
507
///
508
/// The function returns true on success or false if an error occurred.
509
///
510
/// # Safety
511
///
512
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
513
/// `SwapDisableArgs` instance valid for writes that is not externally modified for the duration of
514
/// this call.
515
#[no_mangle]
516
pub unsafe extern "C" fn crosvm_client_swap_disable_vm(args: *mut SwapDisableArgs) -> bool {
517
catch_unwind(|| {
518
if args.is_null() {
519
return false;
520
}
521
let Some(socket_path) = validate_socket_path((*args).socket_path) else {
522
return false;
523
};
524
vms_request(
525
&VmRequest::Swap(SwapCommand::Disable {
526
slow_file_cleanup: (*args).slow_file_cleanup,
527
}),
528
socket_path,
529
)
530
.is_ok()
531
})
532
.unwrap_or(false)
533
}
534
535
/// Trim staging memory for vmm swap for crosvm instance whose control socket is listening on
536
/// `socket_path`.
537
///
538
/// The function returns true on success or false if an error occurred.
539
///
540
/// # Safety
541
///
542
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
543
/// C string that is valid for reads and not modified for the duration of the call.
544
#[no_mangle]
545
pub unsafe extern "C" fn crosvm_client_swap_trim(socket_path: *const c_char) -> bool {
546
catch_unwind(|| {
547
if let Some(socket_path) = validate_socket_path(socket_path) {
548
vms_request(&VmRequest::Swap(SwapCommand::Trim), socket_path).is_ok()
549
} else {
550
false
551
}
552
})
553
.unwrap_or(false)
554
}
555
556
/// Returns vmm-swap status of the crosvm instance whose control socket is listening on
557
/// `socket_path`.
558
///
559
/// The parameters `status` is optional and will only be written to if they are non-null.
560
///
561
/// The function returns true on success or false if an error occurred.
562
///
563
/// # Safety
564
///
565
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
566
/// string that is valid for reads and not modified for the duration of the call, and that `status`
567
/// is a non-null pointer to a `SwapStatus` valid for writes that is not externally modified for
568
/// the duration of the call.
569
#[no_mangle]
570
pub unsafe extern "C" fn crosvm_client_swap_status(
571
socket_path: *const c_char,
572
status: *mut SwapStatus,
573
) -> bool {
574
catch_unwind(|| {
575
if let Some(socket_path) = validate_socket_path(socket_path) {
576
let request = &VmRequest::Swap(SwapCommand::Status);
577
if let Ok(VmResponse::SwapStatus(response)) = handle_request(request, socket_path) {
578
if !status.is_null() {
579
// SAFETY: just checked that `status` is not null.
580
unsafe {
581
*status = response;
582
}
583
}
584
true
585
} else {
586
false
587
}
588
} else {
589
false
590
}
591
})
592
.unwrap_or(false)
593
}
594
595
/// Represents an individual attached USB device.
596
#[repr(C)]
597
pub struct UsbDeviceEntry {
598
/// Internal port index used for identifying this individual device.
599
pub port: u8,
600
/// USB vendor ID
601
pub vendor_id: u16,
602
/// USB product ID
603
pub product_id: u16,
604
}
605
606
impl From<&UsbControlAttachedDevice> for UsbDeviceEntry {
607
fn from(other: &UsbControlAttachedDevice) -> Self {
608
Self {
609
port: other.port,
610
vendor_id: other.vendor_id,
611
product_id: other.product_id,
612
}
613
}
614
}
615
616
/// Simply returns the maximum possible number of USB devices
617
#[no_mangle]
618
pub extern "C" fn crosvm_client_max_usb_devices() -> usize {
619
USB_CONTROL_MAX_PORTS
620
}
621
622
/// Returns all USB devices passed through the crosvm instance whose control socket is listening on
623
/// `socket_path`.
624
///
625
/// The function returns the amount of entries written.
626
/// # Arguments
627
///
628
/// * `socket_path` - Path to the crosvm control socket
629
/// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached
630
/// devices will be written to
631
/// * `entries_length` - Amount of entries in the array specified by `entries`
632
///
633
/// Use the value returned by [`crosvm_client_max_usb_devices()`] to determine the size of the input
634
/// array to this function.
635
///
636
/// # Safety
637
///
638
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
639
/// string that is valid and for reads and not modified for the duration of the call. `entries`
640
/// should be a valid pointer to an array of `UsbDeviceEntry` valid for writes that contains at
641
/// least `entries_length` elements and is not externally modified for the duration of this call.
642
#[no_mangle]
643
pub unsafe extern "C" fn crosvm_client_usb_list(
644
socket_path: *const c_char,
645
entries: *mut UsbDeviceEntry,
646
entries_length: ssize_t,
647
) -> ssize_t {
648
catch_unwind(|| {
649
if let Some(socket_path) = validate_socket_path(socket_path) {
650
if entries.is_null() {
651
return -1;
652
}
653
if let Ok(UsbControlResult::Devices(res)) = do_usb_list(socket_path) {
654
let mut i = 0;
655
for entry in res.iter().filter(|x| x.valid()) {
656
if i >= entries_length {
657
break;
658
}
659
// SAFETY: checked that `entries` is not null.
660
unsafe {
661
*entries.offset(i) = entry.into();
662
i += 1;
663
}
664
}
665
i
666
} else {
667
-1
668
}
669
} else {
670
-1
671
}
672
})
673
.unwrap_or(-1)
674
}
675
676
/// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`.
677
///
678
/// The function returns the amount of entries written.
679
/// # Arguments
680
///
681
/// * `socket_path` - Path to the crosvm control socket
682
/// * `bus` - USB device bus ID (unused)
683
/// * `addr` - USB device address (unused)
684
/// * `vid` - USB device vendor ID (unused)
685
/// * `pid` - USB device product ID (unused)
686
/// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`).
687
/// * `out_port` - (optional) internal port will be written here if provided.
688
///
689
/// The function returns true on success or false if an error occurred.
690
///
691
/// # Safety
692
///
693
/// Function is unsafe due to raw pointer usage.
694
/// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should
695
/// ensure no null pointers are passed into the function.
696
///
697
/// The safety requirements for `socket_path` and `dev_path` are the same as the ones from
698
/// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte
699
/// region.
700
#[no_mangle]
701
pub unsafe extern "C" fn crosvm_client_usb_attach(
702
socket_path: *const c_char,
703
_bus: u8,
704
_addr: u8,
705
_vid: u16,
706
_pid: u16,
707
dev_path: *const c_char,
708
out_port: *mut u8,
709
) -> bool {
710
catch_unwind(|| {
711
if let Some(socket_path) = validate_socket_path(socket_path) {
712
if dev_path.is_null() {
713
return false;
714
}
715
// SAFETY: just checked that `dev_path` is not null.
716
let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or(""));
717
718
if let Ok(UsbControlResult::Ok { port }) = do_usb_attach(socket_path, dev_path) {
719
if !out_port.is_null() {
720
// SAFETY: trivially safe
721
unsafe { *out_port = port };
722
}
723
true
724
} else {
725
false
726
}
727
} else {
728
false
729
}
730
})
731
.unwrap_or(false)
732
}
733
734
/// Attaches a u2f security key to crosvm instance whose control socket is listening on
735
/// `socket_path`.
736
///
737
/// The function returns the amount of entries written.
738
/// # Arguments
739
///
740
/// * `socket_path` - Path to the crosvm control socket
741
/// * `hidraw_path` - Path to the hidraw device of the security key (like `/dev/hidraw0`)
742
/// * `out_port` - (optional) internal port will be written here if provided.
743
///
744
/// The function returns true on success or false if an error occurred.
745
///
746
/// # Safety
747
///
748
/// Function is unsafe due to raw pointer usage.
749
/// Trivial !raw_pointer.is_null() checks prevent some unsafe behavior, but the caller should
750
/// ensure no null pointers are passed into the function.
751
///
752
/// The safety requirements for `socket_path` and `hidraw_path` are the same as the ones from
753
/// `CStr::from_ptr()`. `out_port` should be a non-null pointer that points to a writable 1byte
754
/// region.
755
#[no_mangle]
756
pub unsafe extern "C" fn crosvm_client_security_key_attach(
757
socket_path: *const c_char,
758
hidraw_path: *const c_char,
759
out_port: *mut u8,
760
) -> bool {
761
catch_unwind(|| {
762
if let Some(socket_path) = validate_socket_path(socket_path) {
763
if hidraw_path.is_null() {
764
return false;
765
}
766
let hidraw_path = Path::new(
767
// SAFETY: just checked that `hidraw_path` is not null.
768
unsafe { CStr::from_ptr(hidraw_path) }
769
.to_str()
770
.unwrap_or(""),
771
);
772
773
if let Ok(UsbControlResult::Ok { port }) =
774
do_security_key_attach(socket_path, hidraw_path)
775
{
776
if !out_port.is_null() {
777
// SAFETY: trivially safe
778
unsafe { *out_port = port };
779
}
780
true
781
} else {
782
false
783
}
784
} else {
785
false
786
}
787
})
788
.unwrap_or(false)
789
}
790
791
/// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`.
792
/// `port` determines device to be detached.
793
///
794
/// The function returns true on success or false if an error occurred.
795
///
796
/// # Safety
797
///
798
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
799
/// C string that is valid for reads and not modified for the duration of the call.
800
#[no_mangle]
801
pub unsafe extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool {
802
catch_unwind(|| {
803
if let Some(socket_path) = validate_socket_path(socket_path) {
804
do_usb_detach(socket_path, port).is_ok()
805
} else {
806
false
807
}
808
})
809
.unwrap_or(false)
810
}
811
812
/// Attaches a net tap device to the crosvm instance with control socket at `socket_path`.
813
///
814
/// # Arguments
815
///
816
/// * `socket_path` - Path to the crosvm control socket
817
/// * `tap_name` - Name of the tap device
818
/// * `out_bus_num` - guest bus number will be written here
819
///
820
/// The function returns true on success, false on failure.
821
///
822
/// # Safety
823
///
824
/// Function is unsafe due to raw pointer usage - socket_path and tap_name are assumed to point to a
825
/// null-terminated CStr. Function checks that the pointers are not null, but caller need to check
826
/// the validity of the pointer. out_bus_num is assumed to point to a u8 integer.
827
#[no_mangle]
828
pub unsafe extern "C" fn crosvm_client_net_tap_attach(
829
socket_path: *const c_char,
830
tap_name: *const c_char,
831
out_bus_num: *mut u8,
832
) -> bool {
833
catch_unwind(|| {
834
if let Some(socket_path) = validate_socket_path(socket_path) {
835
if tap_name.is_null() || out_bus_num.is_null() {
836
return false;
837
}
838
// SAFETY: just checked that `tap_name` is not null. Function caller guarantees it
839
// points to a valid CStr.
840
let tap_name = unsafe { CStr::from_ptr(tap_name) }.to_str().unwrap_or("");
841
842
match do_net_add(tap_name, socket_path) {
843
Ok(bus_num) => {
844
// SAFETY: checked out_bus_num is not null. Function caller guarantees
845
// validity of pointer.
846
unsafe { *out_bus_num = bus_num };
847
true
848
}
849
Err(_e) => false,
850
}
851
} else {
852
false
853
}
854
})
855
.unwrap_or(false)
856
}
857
858
/// Detaches a hotplugged tap device from the crosvm instance with control socket at `socket_path`.
859
///
860
/// # Arguments
861
///
862
/// * `socket_path` - Path to the crosvm control socket
863
/// * `bus_num` - Bus number of the tap device to be removed.
864
///
865
/// The function returns true on success, and false on failure.
866
///
867
/// # Safety
868
///
869
/// Function is unsafe due to raw pointer usage - socket_path is assumed to point to a
870
/// null-terminated Cstr. Function checks that the pointers are not null, but caller need to check
871
/// the validity of the pointer.
872
#[no_mangle]
873
pub unsafe extern "C" fn crosvm_client_net_tap_detach(
874
socket_path: *const c_char,
875
bus_num: u8,
876
) -> bool {
877
catch_unwind(|| {
878
if let Some(socket_path) = validate_socket_path(socket_path) {
879
match do_net_remove(bus_num, socket_path) {
880
Ok(()) => true,
881
Err(_e) => false,
882
}
883
} else {
884
false
885
}
886
})
887
.unwrap_or(false)
888
}
889
890
/// Modifies the battery status of crosvm instance whose control socket is listening on
891
/// `socket_path`.
892
///
893
/// The function returns true on success or false if an error occurred.
894
///
895
/// # Safety
896
///
897
/// The caller will ensure the raw pointers in arguments passed in can be safely used by
898
/// `CStr::from_ptr()`
899
#[no_mangle]
900
pub unsafe extern "C" fn crosvm_client_modify_battery(
901
socket_path: *const c_char,
902
battery_type: *const c_char,
903
property: *const c_char,
904
target: *const c_char,
905
) -> bool {
906
catch_unwind(|| {
907
if let Some(socket_path) = validate_socket_path(socket_path) {
908
if battery_type.is_null() || property.is_null() || target.is_null() {
909
return false;
910
}
911
// SAFETY: trivially safe
912
let battery_type = unsafe { CStr::from_ptr(battery_type) };
913
// SAFETY: trivially safe
914
let property = unsafe { CStr::from_ptr(property) };
915
// SAFETY: trivially safe
916
let target = unsafe { CStr::from_ptr(target) };
917
918
do_modify_battery(
919
socket_path,
920
battery_type.to_str().unwrap(),
921
property.to_str().unwrap(),
922
target.to_str().unwrap(),
923
)
924
.is_ok()
925
} else {
926
false
927
}
928
})
929
.unwrap_or(false)
930
}
931
932
/// Fakes the battery status of crosvm instance. The power status will always be on
933
/// battery, and the maximum battery capacity could be read by guest is set to the
934
/// `max_battery_capacity`.
935
///
936
/// The function returns true on success or false if an error occurred.
937
///
938
/// # Arguments
939
///
940
/// * `socket_path` - Path to the crosvm control socket
941
/// * `battery_type` - Type of battery emulation corresponding to vm_tools::BatteryType
942
/// * `max_battery_capacity` - maximum battery capacity could be read by guest
943
///
944
/// # Safety
945
///
946
/// The caller will ensure the raw pointers in arguments passed in can be safely used by
947
/// `CStr::from_ptr()`
948
#[no_mangle]
949
pub unsafe extern "C" fn crosvm_client_fake_power(
950
socket_path: *const c_char,
951
battery_type: *const c_char,
952
max_battery_capacity: u32,
953
) -> bool {
954
catch_unwind(|| {
955
if let Some(socket_path) = validate_socket_path(socket_path) {
956
if battery_type.is_null() || max_battery_capacity > 100 {
957
return false;
958
}
959
960
let battery_type = CStr::from_ptr(battery_type);
961
let fake_max_capacity_target: String = max_battery_capacity.to_string();
962
963
do_modify_battery(
964
socket_path.clone(),
965
battery_type.to_str().unwrap(),
966
&BatProperty::SetFakeBatConfig.to_string(),
967
fake_max_capacity_target.as_str(),
968
)
969
.is_ok()
970
} else {
971
false
972
}
973
})
974
.unwrap_or(false)
975
}
976
977
/// Resume the battery status of crosvm instance from fake status
978
///
979
/// The function returns true on success or false if an error occurred.
980
///
981
/// # Arguments
982
///
983
/// * `socket_path` - Path to the crosvm control socket
984
/// * `battery_type` - Type of battery emulation corresponding to vm_tools::BatteryType
985
///
986
/// # Safety
987
///
988
/// The caller will ensure the raw pointers in arguments passed in can be safely used by
989
/// `CStr::from_ptr()`.
990
#[no_mangle]
991
pub unsafe extern "C" fn crosvm_client_cancel_fake_power(
992
socket_path: *const c_char,
993
battery_type: *const c_char,
994
) -> bool {
995
catch_unwind(|| {
996
if let Some(socket_path) = validate_socket_path(socket_path) {
997
if battery_type.is_null() {
998
return false;
999
}
1000
1001
// SAFETY: the caller has a responsibility of giving a valid char* pointer
1002
let battery_type = CStr::from_ptr(battery_type);
1003
1004
do_modify_battery(
1005
socket_path,
1006
battery_type.to_str().unwrap(),
1007
&BatProperty::CancelFakeBatConfig.to_string(),
1008
"",
1009
)
1010
.is_ok()
1011
} else {
1012
false
1013
}
1014
})
1015
.unwrap_or(false)
1016
}
1017
1018
/// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`.
1019
///
1020
/// The function returns true on success or false if an error occurred.
1021
///
1022
/// # Safety
1023
///
1024
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a
1025
/// C string that is valid for reads and not modified for the duration of the call.
1026
#[no_mangle]
1027
pub unsafe extern "C" fn crosvm_client_resize_disk(
1028
socket_path: *const c_char,
1029
disk_index: u64,
1030
new_size: u64,
1031
) -> bool {
1032
catch_unwind(|| {
1033
if let Some(socket_path) = validate_socket_path(socket_path) {
1034
if let Ok(disk_index) = usize::try_from(disk_index) {
1035
let request = VmRequest::DiskCommand {
1036
disk_index,
1037
command: DiskControlCommand::Resize { new_size },
1038
};
1039
vms_request(&request, socket_path).is_ok()
1040
} else {
1041
false
1042
}
1043
} else {
1044
false
1045
}
1046
})
1047
.unwrap_or(false)
1048
}
1049
1050
/// Similar to internally used `BalloonStats` but using `i64` instead of
1051
/// `Option<u64>`. `None` (or values bigger than `i64::max`) will be encoded as -1.
1052
#[repr(C)]
1053
pub struct BalloonStatsFfi {
1054
pub swap_in: i64,
1055
pub swap_out: i64,
1056
pub major_faults: i64,
1057
pub minor_faults: i64,
1058
pub free_memory: i64,
1059
pub total_memory: i64,
1060
pub available_memory: i64,
1061
pub disk_caches: i64,
1062
pub hugetlb_allocations: i64,
1063
pub hugetlb_failures: i64,
1064
pub shared_memory: i64,
1065
pub unevictable_memory: i64,
1066
}
1067
1068
impl From<&BalloonStats> for BalloonStatsFfi {
1069
fn from(other: &BalloonStats) -> Self {
1070
let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
1071
Self {
1072
swap_in: convert(other.swap_in),
1073
swap_out: convert(other.swap_out),
1074
major_faults: convert(other.major_faults),
1075
minor_faults: convert(other.minor_faults),
1076
free_memory: convert(other.free_memory),
1077
total_memory: convert(other.total_memory),
1078
available_memory: convert(other.available_memory),
1079
disk_caches: convert(other.disk_caches),
1080
hugetlb_allocations: convert(other.hugetlb_allocations),
1081
hugetlb_failures: convert(other.hugetlb_failures),
1082
shared_memory: convert(other.shared_memory),
1083
unevictable_memory: convert(other.unevictable_memory),
1084
}
1085
}
1086
}
1087
1088
/// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`.
1089
///
1090
/// The parameters `stats` and `actual` are optional and will only be written to if they are
1091
/// non-null.
1092
///
1093
/// The function returns true on success or false if an error occurred.
1094
///
1095
/// # Note
1096
///
1097
/// Entries in `BalloonStatsFfi` that are not available will be set to `-1`.
1098
///
1099
/// # Safety
1100
///
1101
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1102
/// string that is valid for reads and not modified for the duration of the call. `stats` should be
1103
/// a pointer to a `BalloonStatsFfi` valid for writes that is not modified for the duration of this
1104
/// call, and `actual` should be a pointer to a `u64` valid for writes that is not modified for the
1105
/// duration of this call.
1106
#[no_mangle]
1107
pub unsafe extern "C" fn crosvm_client_balloon_stats(
1108
socket_path: *const c_char,
1109
stats: *mut BalloonStatsFfi,
1110
actual: *mut u64,
1111
) -> bool {
1112
crosvm_client_balloon_stats_impl(socket_path, None, stats, actual)
1113
}
1114
1115
/// See crosvm_client_balloon_stats.
1116
///
1117
/// # Safety
1118
///
1119
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1120
/// string that is valid for reads and not modified for the duration of the call. `stats` should be
1121
/// a pointer to a `BalloonStatsFfi` valid for writes is not modified for the duration of this
1122
/// call, and `actual` should be a pointer to a `u64` valid for writes that is not modified for the
1123
/// duration of this call.
1124
#[no_mangle]
1125
pub unsafe extern "C" fn crosvm_client_balloon_stats_with_timeout(
1126
socket_path: *const c_char,
1127
timeout_ms: u64,
1128
stats: *mut BalloonStatsFfi,
1129
actual: *mut u64,
1130
) -> bool {
1131
crosvm_client_balloon_stats_impl(
1132
socket_path,
1133
Some(Duration::from_millis(timeout_ms)),
1134
stats,
1135
actual,
1136
)
1137
}
1138
1139
/// # Safety
1140
///
1141
/// This function is safe when the caller ensures the socket_path raw pointer can be safely passed
1142
/// to `CStr::from_ptr()`. `stats` should be a pointer to a `BalloonStatsFfi` valid for writes that
1143
/// is not modified for the duration of this call, and `actual` should be a pointer to a `u64`
1144
/// valid for writes that is not modified for the duration of this call.
1145
unsafe fn crosvm_client_balloon_stats_impl(
1146
socket_path: *const c_char,
1147
timeout_ms: Option<Duration>,
1148
stats: *mut BalloonStatsFfi,
1149
actual: *mut u64,
1150
) -> bool {
1151
catch_unwind(|| {
1152
if let Some(socket_path) = validate_socket_path(socket_path) {
1153
let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
1154
let resp = handle_request_with_timeout(request, socket_path, timeout_ms);
1155
if let Ok(VmResponse::BalloonStats {
1156
stats: ref balloon_stats,
1157
balloon_actual,
1158
}) = resp
1159
{
1160
if !stats.is_null() {
1161
// SAFETY: just checked that `stats` is not null.
1162
unsafe {
1163
*stats = balloon_stats.into();
1164
}
1165
}
1166
1167
if !actual.is_null() {
1168
// SAFETY: just checked that `actual` is not null.
1169
unsafe {
1170
*actual = balloon_actual;
1171
}
1172
}
1173
true
1174
} else {
1175
false
1176
}
1177
} else {
1178
false
1179
}
1180
})
1181
.unwrap_or(false)
1182
}
1183
1184
/// Externally exposed variant of BalloonWS/WSBucket, used for FFI.
1185
#[derive(Clone, Copy, Debug)]
1186
#[repr(C)]
1187
pub struct WorkingSetBucketFfi {
1188
pub age: u64,
1189
pub bytes: [u64; 2],
1190
}
1191
1192
impl WorkingSetBucketFfi {
1193
fn new() -> Self {
1194
Self {
1195
age: 0,
1196
bytes: [0, 0],
1197
}
1198
}
1199
}
1200
1201
impl From<WSBucket> for WorkingSetBucketFfi {
1202
fn from(other: WSBucket) -> Self {
1203
Self {
1204
age: other.age,
1205
bytes: other.bytes,
1206
}
1207
}
1208
}
1209
1210
#[repr(C)]
1211
#[derive(Debug)]
1212
pub struct BalloonWSFfi {
1213
pub ws: [WorkingSetBucketFfi; VIRTIO_BALLOON_WS_MAX_NUM_BINS],
1214
pub num_bins: u8,
1215
pub _reserved: [u8; 7],
1216
}
1217
1218
impl TryFrom<&BalloonWS> for BalloonWSFfi {
1219
type Error = &'static str;
1220
1221
fn try_from(value: &BalloonWS) -> Result<Self, Self::Error> {
1222
if value.ws.len() > VIRTIO_BALLOON_WS_MAX_NUM_BINS {
1223
return Err("too many WS buckets in source object.");
1224
}
1225
1226
let mut ffi = Self {
1227
ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS],
1228
num_bins: value.ws.len() as u8,
1229
..Default::default()
1230
};
1231
for (ffi_ws, other_ws) in ffi.ws.iter_mut().zip(value.ws.iter()) {
1232
*ffi_ws = (*other_ws).into();
1233
}
1234
Ok(ffi)
1235
}
1236
}
1237
1238
impl BalloonWSFfi {
1239
pub fn new() -> Self {
1240
Self {
1241
ws: [WorkingSetBucketFfi::new(); VIRTIO_BALLOON_WS_MAX_NUM_BINS],
1242
num_bins: 0,
1243
_reserved: [0; 7],
1244
}
1245
}
1246
}
1247
1248
impl Default for BalloonWSFfi {
1249
fn default() -> Self {
1250
Self::new()
1251
}
1252
}
1253
1254
#[repr(C)]
1255
pub struct BalloonWSRConfigFfi {
1256
pub intervals: [u64; VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS],
1257
pub num_intervals: u8,
1258
pub _reserved: [u8; 7],
1259
pub refresh_threshold: u64,
1260
pub report_threshold: u64,
1261
}
1262
1263
/// Returns balloon working set of the crosvm instance whose control socket is listening on
1264
/// socket_path.
1265
///
1266
/// The function returns true on success or false if an error occurred.
1267
///
1268
/// # Safety
1269
///
1270
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1271
/// string that is valid for reads and not modified for the duration of the call. `ws` and `actual`
1272
/// should be pointers to a `BalloonStatsFfi` and `u64` respectively that are valid for writes and
1273
/// not modified for the duration of this call.
1274
#[no_mangle]
1275
pub unsafe extern "C" fn crosvm_client_balloon_working_set(
1276
socket_path: *const c_char,
1277
ws: *mut BalloonWSFfi,
1278
actual: *mut u64,
1279
) -> bool {
1280
catch_unwind(|| {
1281
if let Some(socket_path) = validate_socket_path(socket_path) {
1282
let request = &VmRequest::BalloonCommand(BalloonControlCommand::WorkingSet);
1283
if let Ok(VmResponse::BalloonWS {
1284
ws: ref balloon_ws,
1285
balloon_actual,
1286
}) = handle_request(request, socket_path)
1287
{
1288
if !ws.is_null() {
1289
// SAFETY: just checked that `ws` is not null.
1290
unsafe {
1291
*ws = match balloon_ws.try_into() {
1292
Ok(result) => result,
1293
Err(_) => return false,
1294
};
1295
}
1296
}
1297
1298
if !actual.is_null() {
1299
// SAFETY: just checked that `actual` is not null.
1300
unsafe {
1301
*actual = balloon_actual;
1302
}
1303
}
1304
true
1305
} else {
1306
false
1307
}
1308
} else {
1309
false
1310
}
1311
})
1312
.unwrap_or(false)
1313
}
1314
1315
/// Publically exposed version of RegisteredEvent enum, implemented as an
1316
/// integral newtype for FFI safety.
1317
#[repr(C)]
1318
#[derive(Copy, Clone, PartialEq, Eq)]
1319
pub struct RegisteredEventFfi(u32);
1320
1321
pub const REGISTERED_EVENT_VIRTIO_BALLOON_WS_REPORT: RegisteredEventFfi = RegisteredEventFfi(0);
1322
pub const REGISTERED_EVENT_VIRTIO_BALLOON_RESIZE: RegisteredEventFfi = RegisteredEventFfi(1);
1323
pub const REGISTERED_EVENT_VIRTIO_BALLOON_OOM_DEFLATION: RegisteredEventFfi = RegisteredEventFfi(2);
1324
1325
impl TryFrom<RegisteredEventFfi> for RegisteredEvent {
1326
type Error = &'static str;
1327
1328
fn try_from(value: RegisteredEventFfi) -> Result<Self, Self::Error> {
1329
match value.0 {
1330
0 => Ok(RegisteredEvent::VirtioBalloonWsReport),
1331
1 => Ok(RegisteredEvent::VirtioBalloonResize),
1332
2 => Ok(RegisteredEvent::VirtioBalloonOOMDeflation),
1333
_ => Err("RegisteredEventFFi outside of known RegisteredEvent enum range"),
1334
}
1335
}
1336
}
1337
1338
/// Registers the connected process as a listener for `event`.
1339
///
1340
/// The function returns true on success or false if an error occurred.
1341
///
1342
/// # Safety
1343
///
1344
/// Function is unsafe due to raw pointer usage - `socket_path` and `listening_socket_path` should
1345
/// be a non-null pointers to C strings that are valid for reads and not modified for the duration
1346
/// of the call.
1347
#[no_mangle]
1348
pub unsafe extern "C" fn crosvm_client_register_events_listener(
1349
socket_path: *const c_char,
1350
listening_socket_path: *const c_char,
1351
event: RegisteredEventFfi,
1352
) -> bool {
1353
catch_unwind(|| {
1354
if let Some(socket_path) = validate_socket_path(socket_path) {
1355
if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1356
if let Ok(event) = event.try_into() {
1357
let request = VmRequest::RegisterListener {
1358
event,
1359
socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1360
};
1361
vms_request(&request, socket_path).is_ok()
1362
} else {
1363
false
1364
}
1365
} else {
1366
false
1367
}
1368
} else {
1369
false
1370
}
1371
})
1372
.unwrap_or(false)
1373
}
1374
1375
/// Unegisters the connected process as a listener for `event`.
1376
///
1377
/// The function returns true on success or false if an error occurred.
1378
///
1379
/// # Safety
1380
///
1381
/// Function is unsafe due to raw pointer usage - `socket_path` and `listening_socket_path` should
1382
/// be a non-null pointers to C strings that are valid for reads and not modified for the duration
1383
/// of the call.
1384
#[no_mangle]
1385
pub unsafe extern "C" fn crosvm_client_unregister_events_listener(
1386
socket_path: *const c_char,
1387
listening_socket_path: *const c_char,
1388
event: RegisteredEventFfi,
1389
) -> bool {
1390
catch_unwind(|| {
1391
if let Some(socket_path) = validate_socket_path(socket_path) {
1392
if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1393
if let Ok(event) = event.try_into() {
1394
let request = VmRequest::UnregisterListener {
1395
event,
1396
socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1397
};
1398
vms_request(&request, socket_path).is_ok()
1399
} else {
1400
false
1401
}
1402
} else {
1403
false
1404
}
1405
} else {
1406
false
1407
}
1408
})
1409
.unwrap_or(false)
1410
}
1411
1412
/// Unegisters the connected process as a listener for all events.
1413
///
1414
/// The function returns true on success or false if an error occurred.
1415
///
1416
/// # Safety
1417
///
1418
/// Function is unsafe due to raw pointer usage - `socket_path` and `listening_socket_path` should
1419
/// be a non-null pointers to C strings that are valid for reads and not modified for the duration
1420
/// of the call.
1421
#[no_mangle]
1422
pub unsafe extern "C" fn crosvm_client_unregister_listener(
1423
socket_path: *const c_char,
1424
listening_socket_path: *const c_char,
1425
) -> bool {
1426
catch_unwind(|| {
1427
if let Some(socket_path) = validate_socket_path(socket_path) {
1428
if let Some(listening_socket_path) = validate_socket_path(listening_socket_path) {
1429
let request = VmRequest::Unregister {
1430
socket_addr: listening_socket_path.to_str().unwrap().to_string(),
1431
};
1432
vms_request(&request, socket_path).is_ok()
1433
} else {
1434
false
1435
}
1436
} else {
1437
false
1438
}
1439
})
1440
.unwrap_or(false)
1441
}
1442
1443
/// Set Working Set Reporting config in guest.
1444
///
1445
/// The function returns true on success or false if an error occurred.
1446
///
1447
/// # Safety
1448
///
1449
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1450
/// string that is valid for reads and not modified for the duration of the call. `config` should
1451
/// be a pointer to a `BalloonWSRConfigFfi` valid for reads that is not modified for the duration
1452
/// of this call.
1453
#[no_mangle]
1454
pub unsafe extern "C" fn crosvm_client_balloon_wsr_config(
1455
socket_path: *const c_char,
1456
config: *const BalloonWSRConfigFfi,
1457
) -> bool {
1458
catch_unwind(|| {
1459
if let Some(socket_path) = validate_socket_path(socket_path) {
1460
if !config.is_null() {
1461
// SAFETY: just checked that `config` is not null.
1462
unsafe {
1463
if (*config).num_intervals > VIRTIO_BALLOON_WS_MAX_NUM_INTERVALS as u8 {
1464
return false;
1465
}
1466
let mut actual_bins = vec![];
1467
for idx in 0..(*config).num_intervals {
1468
actual_bins.push((*config).intervals[idx as usize]);
1469
}
1470
let refresh_threshold = match u32::try_from((*config).refresh_threshold) {
1471
Ok(r_t) => r_t,
1472
Err(_) => return false,
1473
};
1474
let report_threshold = match u32::try_from((*config).report_threshold) {
1475
Ok(r_p) => r_p,
1476
Err(_) => return false,
1477
};
1478
let request =
1479
VmRequest::BalloonCommand(BalloonControlCommand::WorkingSetConfig {
1480
bins: actual_bins
1481
.iter()
1482
.map(|&b| u32::try_from(b).unwrap())
1483
.collect(),
1484
refresh_threshold,
1485
report_threshold,
1486
});
1487
vms_request(&request, socket_path).is_ok()
1488
}
1489
} else {
1490
false
1491
}
1492
} else {
1493
false
1494
}
1495
})
1496
.unwrap_or(false)
1497
}
1498
1499
/// Publicly exposed version enumeration of hypervisors, implemented as an
1500
/// integral newtype for FFI safety.
1501
#[repr(C)]
1502
#[derive(Copy, Clone, PartialEq, Eq)]
1503
pub struct HypervisorFfi(u32);
1504
1505
pub const HYPERVISOR_KVM: HypervisorFfi = HypervisorFfi(0);
1506
1507
impl TryFrom<&HypervisorKind> for HypervisorFfi {
1508
type Error = &'static str;
1509
1510
fn try_from(hypervisor: &HypervisorKind) -> Result<Self, Self::Error> {
1511
match hypervisor {
1512
HypervisorKind::Kvm => Ok(HYPERVISOR_KVM),
1513
_ => Err("unsupported hypervisor"),
1514
}
1515
}
1516
}
1517
1518
/// Hypervisor specific unique identifier of a VM.
1519
#[repr(C)]
1520
pub union HypervisorSpecificVmDescriptorFfi {
1521
// We use c_int instead of RawFd here because the std::os::fd crate is only available on unix
1522
// platforms.
1523
pub vm_fd: c_int,
1524
pub _reserved: u64,
1525
}
1526
1527
/// A unique identifier of a VM.
1528
#[repr(C)]
1529
pub struct VmDescriptorFfi {
1530
pub hypervisor: HypervisorFfi,
1531
pub descriptor: HypervisorSpecificVmDescriptorFfi,
1532
}
1533
1534
/// Get a descriptor representing a running VM.
1535
///
1536
/// The function returns true on success or false if an error occurred.
1537
///
1538
/// # Safety
1539
///
1540
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1541
/// string that is valid for reads and not modified for the duration of the call. `vm_desc_out`
1542
/// should be a pointer to a `VmDescriptorFfi` valid for writes that is not externally modified for
1543
/// the duration of this call.
1544
#[no_mangle]
1545
pub unsafe extern "C" fn crosvm_get_vm_descriptor(
1546
socket_path: *const c_char,
1547
vm_desc_out: *mut VmDescriptorFfi,
1548
) -> bool {
1549
catch_unwind(|| {
1550
let Some(socket_path) = validate_socket_path(socket_path) else {
1551
return false;
1552
};
1553
1554
if vm_desc_out.is_null() {
1555
return false;
1556
}
1557
1558
let resp = handle_request(&VmRequest::GetVmDescriptor, socket_path);
1559
if let Ok(VmResponse::VmDescriptor { hypervisor, vm_fd }) = resp {
1560
let Ok(hypervisor) = HypervisorFfi::try_from(&hypervisor) else {
1561
return false;
1562
};
1563
// SAFETY: just checked that `vm_desc_out` is not null.
1564
(*vm_desc_out).hypervisor = hypervisor;
1565
// On windows platforms RawDescriptor is actually a *mut c_void, hence cast to c_int
1566
// here.
1567
(*vm_desc_out).descriptor.vm_fd = vm_fd.into_raw_descriptor() as c_int;
1568
true
1569
} else {
1570
false
1571
}
1572
})
1573
.unwrap_or(false)
1574
}
1575
1576
/// Platform agnostic wrapper over a file descriptor.
1577
#[repr(C)]
1578
pub union FdWrapper {
1579
/// File descriptor on linux systems.
1580
pub linux_fd: c_int,
1581
/// File descriptor on windows systems.
1582
pub windows_fd: *mut c_void,
1583
}
1584
1585
/// Arguments structure for crosvm_add_memory.
1586
#[repr(C)]
1587
pub struct AddMemoryArgs {
1588
/// File descriptor representing memory (e.g. memfd or dma_buf_fd) shared with the VM.
1589
pub fd: FdWrapper,
1590
/// Offset.
1591
pub offset: u64,
1592
/// Start of the memory range in the guest VM that this memory will be mapped to.
1593
pub range_start: u64,
1594
/// End of the memory range in the guest VM that this memory will be mapped to.
1595
pub range_end: u64,
1596
/// Whether this memory is cache coherent or not.
1597
pub cache_coherent: bool,
1598
/// Padding for the future extensions.
1599
// TODO(ioffe): is one u64 enough?
1600
pub _reserved: u64,
1601
}
1602
1603
/// Registers memory represented by `memory_args` to the guest VM.
1604
///
1605
/// The function returns true on success or false if an error occurred. On success the
1606
/// `out_region_id` will contain the unique id representing the registered memory in guest.
1607
///
1608
/// # Safety
1609
///
1610
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1611
/// string that is valid for reads and not modified for the duration of the call. `memory_args`
1612
/// should be a pointer to `AddMemoryArgs` struct valid for read and not modified for the duration
1613
/// of this call. `out_region_id` should be a pointer to a `u64` valid for writes that is not
1614
/// externally modified for the duration of this call.
1615
/// This function takes the ownership of the `memory_args.fd` file descriptor.
1616
#[no_mangle]
1617
pub unsafe extern "C" fn crosvm_register_memory(
1618
socket_path: *const c_char,
1619
memory_args: *const AddMemoryArgs,
1620
out_region_id: *mut u64,
1621
) -> bool {
1622
catch_unwind(|| {
1623
// SAFETY: `memory_args.fd` is valid during the duration of this function.
1624
let fd = unsafe {
1625
#[cfg(not(target_os = "windows"))]
1626
{
1627
SafeDescriptor::from_raw_descriptor((*memory_args).fd.linux_fd)
1628
}
1629
#[cfg(target_os = "windows")]
1630
{
1631
SafeDescriptor::from_raw_descriptor((*memory_args).fd.windows_fd)
1632
}
1633
};
1634
1635
let Some(socket_path) = validate_socket_path(socket_path) else {
1636
return false;
1637
};
1638
1639
if out_region_id.is_null() {
1640
return false;
1641
}
1642
1643
let req = VmRequest::RegisterMemory {
1644
fd,
1645
offset: (*memory_args).offset,
1646
range_start: (*memory_args).range_start,
1647
range_end: (*memory_args).range_end,
1648
cache_coherent: (*memory_args).cache_coherent,
1649
};
1650
let resp = handle_request(&req, socket_path);
1651
if let Ok(VmResponse::RegisterMemory2 { region_id }) = resp {
1652
*out_region_id = region_id;
1653
true
1654
} else {
1655
false
1656
}
1657
})
1658
.unwrap_or(false)
1659
}
1660
1661
/// Unregisters memory represented by the `region_id` from the guest IPA space.
1662
///
1663
/// The function returns true on success or false if an error occurred.
1664
///
1665
/// # Safety
1666
///
1667
/// Function is unsafe due to raw pointer usage - `socket_path` should be a non-null pointer to a C
1668
/// string that is valid for reads and not modified for the duration of the call.
1669
#[no_mangle]
1670
pub unsafe extern "C" fn crosvm_unregister_memory(
1671
socket_path: *const c_char,
1672
region_id: u64,
1673
) -> bool {
1674
catch_unwind(|| {
1675
let Some(socket_path) = validate_socket_path(socket_path) else {
1676
return false;
1677
};
1678
1679
let req = VmRequest::UnregisterMemory { region_id };
1680
let resp = handle_request(&req, socket_path);
1681
matches!(resp, Ok(VmResponse::Ok))
1682
})
1683
.unwrap_or(false)
1684
}
1685
1686