• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 The Chromium OS Authors. All rights reserved.
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 // Usually you would need to invoke crosvm with subcommands and you'd get the result on
7 // stdout.
8 use std::convert::{TryFrom, TryInto};
9 use std::ffi::CStr;
10 use std::panic::catch_unwind;
11 use std::path::{Path, PathBuf};
12 
13 use libc::{c_char, ssize_t};
14 
15 use vm_control::{
16     client::*, BalloonControlCommand, BalloonStats, DiskControlCommand, UsbControlAttachedDevice,
17     UsbControlResult, VmRequest, VmResponse,
18 };
19 
validate_socket_path(socket_path: *const c_char) -> Option<PathBuf>20 fn validate_socket_path(socket_path: *const c_char) -> Option<PathBuf> {
21     if !socket_path.is_null() {
22         let socket_path = unsafe { CStr::from_ptr(socket_path) };
23         Some(PathBuf::from(socket_path.to_str().ok()?))
24     } else {
25         None
26     }
27 }
28 
29 /// Stops the crosvm instance whose control socket is listening on `socket_path`.
30 ///
31 /// The function returns true on success or false if an error occured.
32 #[no_mangle]
crosvm_client_stop_vm(socket_path: *const c_char) -> bool33 pub extern "C" fn crosvm_client_stop_vm(socket_path: *const c_char) -> bool {
34     catch_unwind(|| {
35         if let Some(socket_path) = validate_socket_path(socket_path) {
36             vms_request(&VmRequest::Exit, &socket_path).is_ok()
37         } else {
38             false
39         }
40     })
41     .unwrap_or(false)
42 }
43 
44 /// Suspends the crosvm instance whose control socket is listening on `socket_path`.
45 ///
46 /// The function returns true on success or false if an error occured.
47 #[no_mangle]
crosvm_client_suspend_vm(socket_path: *const c_char) -> bool48 pub extern "C" fn crosvm_client_suspend_vm(socket_path: *const c_char) -> bool {
49     catch_unwind(|| {
50         if let Some(socket_path) = validate_socket_path(socket_path) {
51             vms_request(&VmRequest::Suspend, &socket_path).is_ok()
52         } else {
53             false
54         }
55     })
56     .unwrap_or(false)
57 }
58 
59 /// Resumes the crosvm instance whose control socket is listening on `socket_path`.
60 ///
61 /// The function returns true on success or false if an error occured.
62 #[no_mangle]
crosvm_client_resume_vm(socket_path: *const c_char) -> bool63 pub extern "C" fn crosvm_client_resume_vm(socket_path: *const c_char) -> bool {
64     catch_unwind(|| {
65         if let Some(socket_path) = validate_socket_path(socket_path) {
66             vms_request(&VmRequest::Resume, &socket_path).is_ok()
67         } else {
68             false
69         }
70     })
71     .unwrap_or(false)
72 }
73 
74 /// Adjusts the balloon size of the crosvm instance whose control socket is
75 /// listening on `socket_path`.
76 ///
77 /// The function returns true on success or false if an error occured.
78 #[no_mangle]
crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool79 pub extern "C" fn crosvm_client_balloon_vms(socket_path: *const c_char, num_bytes: u64) -> bool {
80     catch_unwind(|| {
81         if let Some(socket_path) = validate_socket_path(socket_path) {
82             let command = BalloonControlCommand::Adjust { num_bytes };
83             vms_request(&VmRequest::BalloonCommand(command), &socket_path).is_ok()
84         } else {
85             false
86         }
87     })
88     .unwrap_or(false)
89 }
90 
91 /// Represents an individual attached USB device.
92 #[repr(C)]
93 pub struct UsbDeviceEntry {
94     /// Internal port index used for identifying this individual device.
95     port: u8,
96     /// USB vendor ID
97     vendor_id: u16,
98     /// USB product ID
99     product_id: u16,
100 }
101 
102 impl From<&UsbControlAttachedDevice> for UsbDeviceEntry {
from(other: &UsbControlAttachedDevice) -> Self103     fn from(other: &UsbControlAttachedDevice) -> Self {
104         Self {
105             port: other.port,
106             vendor_id: other.vendor_id,
107             product_id: other.product_id,
108         }
109     }
110 }
111 
112 /// Returns all USB devices passed through the crosvm instance whose control socket is listening on `socket_path`.
113 ///
114 /// The function returns the amount of entries written.
115 /// # Arguments
116 ///
117 /// * `socket_path` - Path to the crosvm control socket
118 /// * `entries` - Pointer to an array of `UsbDeviceEntry` where the details about the attached
119 ///               devices will be written to
120 /// * `entries_length` - Amount of entries in the array specified by `entries`
121 ///
122 /// Crosvm supports passing through up to 255 devices, so pasing an array with 255 entries will
123 /// guarantee to return all entries.
124 #[no_mangle]
crosvm_client_usb_list( socket_path: *const c_char, entries: *mut UsbDeviceEntry, entries_length: ssize_t, ) -> ssize_t125 pub extern "C" fn crosvm_client_usb_list(
126     socket_path: *const c_char,
127     entries: *mut UsbDeviceEntry,
128     entries_length: ssize_t,
129 ) -> ssize_t {
130     catch_unwind(|| {
131         if let Some(socket_path) = validate_socket_path(socket_path) {
132             if let Ok(UsbControlResult::Devices(res)) = do_usb_list(&socket_path) {
133                 let mut i = 0;
134                 for entry in res.iter().filter(|x| x.valid()) {
135                     if i >= entries_length {
136                         break;
137                     }
138                     unsafe {
139                         *entries.offset(i) = entry.into();
140                         i += 1;
141                     }
142                 }
143                 i
144             } else {
145                 -1
146             }
147         } else {
148             -1
149         }
150     })
151     .unwrap_or(-1)
152 }
153 
154 /// Attaches an USB device to crosvm instance whose control socket is listening on `socket_path`.
155 ///
156 /// The function returns the amount of entries written.
157 /// # Arguments
158 ///
159 /// * `socket_path` - Path to the crosvm control socket
160 /// * `bus` - USB device bus ID
161 /// * `addr` - USB device address
162 /// * `vid` - USB device vendor ID
163 /// * `pid` - USB device product ID
164 /// * `dev_path` - Path to the USB device (Most likely `/dev/bus/usb/<bus>/<addr>`).
165 /// * `out_port` - (optional) internal port will be written here if provided.
166 ///
167 /// The function returns true on success or false if an error occured.
168 #[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, ) -> bool169 pub extern "C" fn crosvm_client_usb_attach(
170     socket_path: *const c_char,
171     bus: u8,
172     addr: u8,
173     vid: u16,
174     pid: u16,
175     dev_path: *const c_char,
176     out_port: *mut u8,
177 ) -> bool {
178     catch_unwind(|| {
179         if let Some(socket_path) = validate_socket_path(socket_path) {
180             if dev_path.is_null() {
181                 return false;
182             }
183             let dev_path = Path::new(unsafe { CStr::from_ptr(dev_path) }.to_str().unwrap_or(""));
184 
185             if let Ok(UsbControlResult::Ok { port }) =
186                 do_usb_attach(&socket_path, bus, addr, vid, pid, dev_path)
187             {
188                 if !out_port.is_null() {
189                     unsafe { *out_port = port };
190                 }
191                 true
192             } else {
193                 false
194             }
195         } else {
196             false
197         }
198     })
199     .unwrap_or(false)
200 }
201 
202 /// Detaches an USB device from crosvm instance whose control socket is listening on `socket_path`.
203 /// `port` determines device to be detached.
204 ///
205 /// The function returns true on success or false if an error occured.
206 #[no_mangle]
crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool207 pub extern "C" fn crosvm_client_usb_detach(socket_path: *const c_char, port: u8) -> bool {
208     catch_unwind(|| {
209         if let Some(socket_path) = validate_socket_path(socket_path) {
210             do_usb_detach(&socket_path, port).is_ok()
211         } else {
212             false
213         }
214     })
215     .unwrap_or(false)
216 }
217 
218 /// Modifies the battery status of crosvm instance whose control socket is listening on
219 /// `socket_path`.
220 ///
221 /// The function returns true on success or false if an error occured.
222 #[no_mangle]
crosvm_client_modify_battery( socket_path: *const c_char, battery_type: *const c_char, property: *const c_char, target: *const c_char, ) -> bool223 pub extern "C" fn crosvm_client_modify_battery(
224     socket_path: *const c_char,
225     battery_type: *const c_char,
226     property: *const c_char,
227     target: *const c_char,
228 ) -> bool {
229     catch_unwind(|| {
230         if let Some(socket_path) = validate_socket_path(socket_path) {
231             if battery_type.is_null() || property.is_null() || target.is_null() {
232                 return false;
233             }
234             let battery_type = unsafe { CStr::from_ptr(battery_type) };
235             let property = unsafe { CStr::from_ptr(property) };
236             let target = unsafe { CStr::from_ptr(target) };
237 
238             do_modify_battery(
239                 &socket_path,
240                 battery_type.to_str().unwrap(),
241                 property.to_str().unwrap(),
242                 target.to_str().unwrap(),
243             )
244             .is_ok()
245         } else {
246             false
247         }
248     })
249     .unwrap_or(false)
250 }
251 
252 /// Resizes the disk of the crosvm instance whose control socket is listening on `socket_path`.
253 ///
254 /// The function returns true on success or false if an error occured.
255 #[no_mangle]
crosvm_client_resize_disk( socket_path: *const c_char, disk_index: u64, new_size: u64, ) -> bool256 pub extern "C" fn crosvm_client_resize_disk(
257     socket_path: *const c_char,
258     disk_index: u64,
259     new_size: u64,
260 ) -> bool {
261     catch_unwind(|| {
262         if let Some(socket_path) = validate_socket_path(socket_path) {
263             if let Ok(disk_index) = usize::try_from(disk_index) {
264                 let request = VmRequest::DiskCommand {
265                     disk_index,
266                     command: DiskControlCommand::Resize { new_size },
267                 };
268                 vms_request(&request, &socket_path).is_ok()
269             } else {
270                 false
271             }
272         } else {
273             false
274         }
275     })
276     .unwrap_or(false)
277 }
278 
279 /// Similar to internally used `BalloonStats` but using i64 instead of
280 /// Option<u64>. `None` (or values bigger than i64::max) will be encoded as -1.
281 #[repr(C)]
282 pub struct BalloonStatsFfi {
283     swap_in: i64,
284     swap_out: i64,
285     major_faults: i64,
286     minor_faults: i64,
287     free_memory: i64,
288     total_memory: i64,
289     available_memory: i64,
290     disk_caches: i64,
291     hugetlb_allocations: i64,
292     hugetlb_failures: i64,
293 }
294 
295 impl From<&BalloonStats> for BalloonStatsFfi {
from(other: &BalloonStats) -> Self296     fn from(other: &BalloonStats) -> Self {
297         let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
298         Self {
299             swap_in: convert(other.swap_in),
300             swap_out: convert(other.swap_out),
301             major_faults: convert(other.major_faults),
302             minor_faults: convert(other.minor_faults),
303             free_memory: convert(other.free_memory),
304             total_memory: convert(other.total_memory),
305             available_memory: convert(other.available_memory),
306             disk_caches: convert(other.disk_caches),
307             hugetlb_allocations: convert(other.hugetlb_allocations),
308             hugetlb_failures: convert(other.hugetlb_failures),
309         }
310     }
311 }
312 
313 /// Returns balloon stats of the crosvm instance whose control socket is listening on `socket_path`.
314 ///
315 /// The parameters `stats` and `actual` are optional and will only be written to if they are
316 /// non-null.
317 ///
318 /// The function returns true on success or false if an error occured.
319 ///
320 /// # Note
321 ///
322 /// Entries in `BalloonStatsFfi` that are not available will be set to `-1`.
323 #[no_mangle]
crosvm_client_balloon_stats( socket_path: *const c_char, stats: *mut BalloonStatsFfi, actual: *mut u64, ) -> bool324 pub extern "C" fn crosvm_client_balloon_stats(
325     socket_path: *const c_char,
326     stats: *mut BalloonStatsFfi,
327     actual: *mut u64,
328 ) -> bool {
329     catch_unwind(|| {
330         if let Some(socket_path) = validate_socket_path(socket_path) {
331             let request = &VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
332             if let Ok(VmResponse::BalloonStats {
333                 stats: ref balloon_stats,
334                 balloon_actual,
335             }) = handle_request(request, &socket_path)
336             {
337                 if !stats.is_null() {
338                     unsafe {
339                         *stats = balloon_stats.into();
340                     }
341                 }
342 
343                 if !actual.is_null() {
344                     unsafe {
345                         *actual = balloon_actual;
346                     }
347                 }
348                 true
349             } else {
350                 false
351             }
352         } else {
353             false
354         }
355     })
356     .unwrap_or(false)
357 }
358