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