1use std::path::Path;
5use std::sync::{Arc, Mutex};
6#[cfg(target_os = "linux")]
7use std::time::Duration;
8
9use hyperlight_common::func::{ParameterTuple, SupportedReturnType};
10use tracing_core::LevelFilter;
11
12use crate::func::HostFunction;
13use crate::mem::memory_region::{MemoryRegion, MemoryRegionFlags};
14use crate::sandbox::SandboxConfiguration;
15#[cfg(gdb)]
16use crate::sandbox::config::DebugInfo;
17#[cfg(target_arch = "x86_64")]
18use crate::sandbox::config::GuestMsrError;
19use crate::sandbox::host_funcs::FunctionEntry;
20use crate::sandbox::snapshot::Snapshot;
21use crate::sandbox::uninitialized::{GuestBlob, GuestEnvironment};
22use crate::{
23 GuestBinary, HostFunctions, MultiUseSandbox as Sandbox, Result, UninitializedSandbox, new_error,
24};
25
26enum Source {
28 GuestBinary(GuestBinary),
29 Snapshot(Arc<Snapshot>),
30}
31
32impl Source {
33 fn file(path: impl AsRef<Path>) -> Self {
34 Self::GuestBinary(GuestBinary::FilePath(path.as_ref().to_path_buf()))
35 }
36
37 fn bytes(buffer: impl Into<Vec<u8>>) -> Self {
38 Self::GuestBinary(GuestBinary::Buffer(buffer.into()))
39 }
40}
41
42pub struct SandboxBuilder {
90 source: Source,
91 cfg: SandboxConfiguration,
92 host_funcs: HostFunctions,
93 init_data: Option<(Vec<u8>, MemoryRegionFlags)>,
94 mapped_file_cow: Vec<(std::path::PathBuf, u64)>,
95 mapped_memory_regions: Vec<MemoryRegion>,
96 guest_log_level: Option<LevelFilter>,
97}
98
99impl SandboxBuilder {
100 fn with_source(source: Source) -> Self {
101 Self {
102 source,
103 cfg: SandboxConfiguration::default(),
104 host_funcs: HostFunctions::default(),
105 init_data: None,
106 mapped_file_cow: Vec::new(),
107 mapped_memory_regions: Vec::new(),
108 guest_log_level: None,
109 }
110 }
111
112 pub fn from_file(path: impl AsRef<Path>) -> Self {
114 Self::with_source(Source::file(path))
115 }
116
117 pub fn from_bytes(buffer: impl Into<Vec<u8>>) -> Self {
120 Self::with_source(Source::bytes(buffer))
121 }
122
123 pub fn from_snapshot(snapshot: Arc<Snapshot>) -> Self {
125 Self::with_source(Source::Snapshot(snapshot))
126 }
127
128 pub fn build(self) -> Result<Sandbox> {
136 let Self {
137 source,
138 cfg,
139 host_funcs,
140 init_data,
141 mapped_file_cow,
142 mapped_memory_regions,
143 guest_log_level,
144 } = self;
145
146 let mut sandbox = match source {
147 Source::GuestBinary(guest_binary) => {
148 let env = GuestEnvironment {
149 init_data: init_data.as_ref().map(|(data, flags)| GuestBlob {
150 data,
151 permissions: *flags,
152 }),
153 guest_binary,
154 };
155
156 let mut uninitialized_sandbox = UninitializedSandbox::new(env, Some(cfg))?;
157
158 uninitialized_sandbox.host_funcs = Arc::new(Mutex::new(host_funcs.into_inner()));
159
160 for (path, guest_base) in mapped_file_cow {
161 uninitialized_sandbox.map_file_cow(&path, guest_base)?;
162 }
163
164 if let Some(log_level) = guest_log_level {
165 uninitialized_sandbox.set_max_guest_log_level(log_level);
166 }
167
168 uninitialized_sandbox.evolve()?
169 }
170 Source::Snapshot(snapshot) => {
171 if init_data.is_some() {
172 return Err(new_error!(
173 "init_data has no effect when building from a snapshot, as the snapshot already contains it"
174 ));
175 }
176
177 if guest_log_level.is_some() {
178 return Err(new_error!(
179 "guest_log_level has no effect when building from a snapshot, as the snapshot already contains it"
180 ));
181 }
182
183 let mut sandbox = Sandbox::from_snapshot(snapshot, host_funcs, Some(cfg))?;
184
185 for (path, guest_base) in mapped_file_cow {
186 sandbox.map_file_cow(&path, guest_base)?;
187 }
188
189 sandbox
190 }
191 };
192
193 for region in mapped_memory_regions {
194 unsafe { sandbox.map_region(®ion)? };
197 }
198
199 Ok(sandbox)
200 }
201}
202
203impl SandboxBuilder {
204 pub fn init_data(mut self, data: impl Into<Vec<u8>>, flags: MemoryRegionFlags) -> Self {
210 self.init_data = Some((data.into(), flags));
211 self
212 }
213
214 pub fn mapped_file_cow(mut self, path: impl AsRef<Path>, guest_base: u64) -> Self {
221 self.mapped_file_cow
222 .push((path.as_ref().to_path_buf(), guest_base));
223 self
224 }
225
226 pub unsafe fn mapped_memory_region(mut self, region: MemoryRegion) -> Self {
237 self.mapped_memory_regions.push(region);
238 self
239 }
240
241 pub fn guest_log_level(mut self, level: LevelFilter) -> Self {
249 self.guest_log_level = Some(level);
250 self
251 }
252
253 pub fn get_guest_log_level(&self) -> Option<LevelFilter> {
255 self.guest_log_level
256 }
257}
258
259impl SandboxBuilder {
260 pub fn host_function<Args: ParameterTuple, Output: SupportedReturnType>(
265 mut self,
266 name: impl AsRef<str>,
267 host_func: impl Into<HostFunction<Output, Args>>,
268 ) -> Self {
269 let func = host_func.into().into();
270 let name = name.as_ref().to_string();
271
272 let entry = FunctionEntry {
273 function: func,
274 parameter_types: Args::TYPE,
275 return_type: Output::TYPE,
276 };
277
278 self.host_funcs
279 .inner_mut()
280 .register_host_function(name, entry);
281 self
282 }
283
284 pub fn host_print(self, print_func: impl Into<HostFunction<i32, (String,)>>) -> Self {
290 self.host_function("HostPrint", print_func)
291 }
292
293 pub fn host_functions(mut self, host_funcs: HostFunctions) -> Self {
300 for (func_name, func_entry) in host_funcs.into_iter() {
301 self.host_funcs
302 .inner_mut()
303 .register_host_function(func_name, func_entry);
304 }
305 self
306 }
307}
308
309impl SandboxBuilder {
310 pub fn input_data_size(mut self, size: usize) -> Self {
313 self.cfg.set_input_data_size(size);
314 self
315 }
316
317 pub fn get_input_data_size(&self) -> usize {
319 self.cfg.get_input_data_size()
320 }
321
322 pub fn output_data_size(mut self, size: usize) -> Self {
325 self.cfg.set_output_data_size(size);
326 self
327 }
328
329 pub fn get_output_data_size(&self) -> usize {
331 self.cfg.get_output_data_size()
332 }
333
334 pub fn heap_size(mut self, size: u64) -> Self {
337 self.cfg.set_heap_size(size);
338 self
339 }
340
341 pub fn get_heap_size(&self) -> u64 {
344 self.cfg.get_heap_size()
345 }
346
347 pub fn scratch_size(mut self, size: usize) -> Self {
349 self.cfg.set_scratch_size(size);
350 self
351 }
352
353 pub fn get_scratch_size(&self) -> usize {
355 self.cfg.get_scratch_size()
356 }
357
358 #[cfg(target_arch = "x86_64")]
370 pub fn guest_msrs(mut self, indices: &[u32]) -> std::result::Result<Self, GuestMsrError> {
371 self.cfg.guest_msrs(indices)?;
372 Ok(self)
373 }
374
375 #[cfg(target_os = "linux")]
377 pub fn interrupt_retry_delay(mut self, delay: Duration) -> Self {
378 self.cfg.set_interrupt_retry_delay(delay);
379 self
380 }
381
382 #[cfg(target_os = "linux")]
384 pub fn get_interrupt_retry_delay(&self) -> Duration {
385 self.cfg.get_interrupt_retry_delay()
386 }
387
388 #[cfg(target_os = "linux")]
395 pub fn interrupt_vcpu_sigrtmin_offset(mut self, offset: u8) -> Result<Self> {
396 self.cfg.set_interrupt_vcpu_sigrtmin_offset(offset)?;
397 Ok(self)
398 }
399
400 #[cfg(target_os = "linux")]
402 pub fn get_interrupt_vcpu_sigrtmin_offset(&self) -> u8 {
403 self.cfg.get_interrupt_vcpu_sigrtmin_offset()
404 }
405
406 #[cfg(crashdump)]
408 pub fn guest_core_dump(mut self, enabled: bool) -> Self {
409 self.cfg.set_guest_core_dump(enabled);
410 self
411 }
412
413 #[cfg(crashdump)]
415 pub fn get_guest_core_dump(&self) -> bool {
416 self.cfg.get_guest_core_dump()
417 }
418
419 #[cfg(gdb)]
421 pub fn guest_debug_info(mut self, debug_info: DebugInfo) -> Self {
422 self.cfg.set_guest_debug_info(debug_info);
423 self
424 }
425
426 #[cfg(gdb)]
428 pub fn get_guest_debug_info(&self) -> Option<DebugInfo> {
429 self.cfg.get_guest_debug_info()
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use hyperlight_testing::simple_guest_as_string;
436 use tracing_core::LevelFilter;
437
438 use super::SandboxBuilder;
439 use crate::mem::memory_region::MemoryRegionFlags;
440
441 #[test]
442 fn build_from_file() {
443 let path = simple_guest_as_string().unwrap();
444 let mut sandbox = SandboxBuilder::from_file(path)
445 .input_data_size(0x8000)
446 .build()
447 .unwrap();
448
449 let result = sandbox.call::<String>("Echo", "hello".to_string()).unwrap();
450 assert_eq!(result, "hello");
451 }
452
453 #[test]
454 fn build_from_bytes() {
455 let bytes = std::fs::read(simple_guest_as_string().unwrap()).unwrap();
456 let mut sandbox = SandboxBuilder::from_bytes(bytes).build().unwrap();
457
458 let result = sandbox.call::<String>("Echo", "hello".to_string()).unwrap();
459 assert_eq!(result, "hello");
460 }
461
462 #[test]
463 fn build_from_snapshot() {
464 let path = simple_guest_as_string().unwrap();
465 let mut sandbox = SandboxBuilder::from_file(path).build().unwrap();
466 let snapshot = sandbox.snapshot().unwrap();
467
468 let mut restored = SandboxBuilder::from_snapshot(snapshot).build().unwrap();
469
470 let result = restored
471 .call::<String>("Echo", "hello".to_string())
472 .unwrap();
473 assert_eq!(result, "hello");
474 }
475
476 #[test]
477 fn build_from_snapshot_errors_on_ignored_settings() {
478 let path = simple_guest_as_string().unwrap();
479 let mut sandbox = SandboxBuilder::from_file(path).build().unwrap();
480 let snapshot = sandbox.snapshot().unwrap();
481
482 assert!(
483 SandboxBuilder::from_snapshot(snapshot.clone())
484 .init_data([0u8; 8], MemoryRegionFlags::READ)
485 .build()
486 .is_err()
487 );
488
489 assert!(
490 SandboxBuilder::from_snapshot(snapshot)
491 .guest_log_level(LevelFilter::INFO)
492 .build()
493 .is_err()
494 );
495 }
496}