@@ -67,6 +67,7 @@ use hyperlight_host::sandbox::SandboxConfiguration;
6767use hyperlight_host:: { GuestBinary , HostFunctions , MultiUseSandbox , UninitializedSandbox } ;
6868use std:: collections:: HashMap ;
6969use std:: path:: Path ;
70+ use std:: sync:: atomic:: { AtomicI32 , Ordering } ;
7071use std:: sync:: Arc ;
7172use std:: time:: Duration ;
7273
@@ -791,6 +792,25 @@ fn build_tools(
791792 Ok ( Some ( registry) )
792793}
793794
795+ /// Register internal tools (`__hl_exit`, `__hl_sleep`) on a tool registry.
796+ /// These are plumbing used by the guest driver (`hl_pydriver.c`) and are
797+ /// always present regardless of user-supplied tools or preopens.
798+ fn register_internal_tools ( tools : & mut ToolRegistry , exit_code : & Arc < AtomicI32 > ) {
799+ let ec = exit_code. clone ( ) ;
800+ tools. register ( "__hl_exit" , move |args| {
801+ let code = args[ "code" ] . as_i64 ( ) . unwrap_or ( 1 ) as i32 ;
802+ ec. store ( code, Ordering :: Relaxed ) ;
803+ Ok ( serde_json:: json!( { } ) )
804+ } ) ;
805+ tools. register ( "__hl_sleep" , |args| {
806+ let ns = args[ "ns" ] . as_u64 ( ) . unwrap_or ( 0 ) ;
807+ if ns > 0 {
808+ std:: thread:: sleep ( std:: time:: Duration :: from_nanos ( ns) ) ;
809+ }
810+ Ok ( serde_json:: json!( { } ) )
811+ } ) ;
812+ }
813+
794814/// Routes incoming fs_* tool calls to the matching `FsSandbox` by
795815/// matching the guest-supplied path against each preopen's guest path.
796816#[ derive( Clone ) ]
@@ -1041,6 +1061,7 @@ pub struct Sandbox {
10411061 /// Snapshot restore unmaps all non-snapshot regions.
10421062 file_mapping_path : Option < std:: path:: PathBuf > ,
10431063 file_mapping_base : u64 ,
1064+ exit_code : Arc < AtomicI32 > ,
10441065}
10451066
10461067/// Where the initrd comes from — either a file (zero-copy `map_file_cow`)
@@ -1213,17 +1234,16 @@ impl Sandbox {
12131234
12141235 let mut usbox = UninitializedSandbox :: new ( env, Some ( config. sandbox_config ( ) ) ) ?;
12151236
1216- let tools = build_tools ( tools, preopens) ?;
1217-
1218- if let Some ( tools) = tools {
1219- let tools = Arc :: new ( tools) ;
1220- let tools_ref = tools. clone ( ) ;
1221- usbox. register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1222- tools_ref. dispatch ( & payload)
1223- } ) ?;
1224- }
1237+ let exit_code = Arc :: new ( AtomicI32 :: new ( 0 ) ) ;
1238+ let mut tools = build_tools ( tools, preopens) ?. unwrap_or_default ( ) ;
1239+ register_internal_tools ( & mut tools, & exit_code) ;
1240+ let tools = Arc :: new ( tools) ;
1241+ let tools_ref = tools. clone ( ) ;
1242+ usbox. register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1243+ tools_ref. dispatch ( & payload)
1244+ } ) ?;
12251245
1226- Self :: finish_evolve ( usbox, None , 0 )
1246+ Self :: finish_evolve ( usbox, None , 0 , exit_code )
12271247 }
12281248
12291249 /// Low-level: boot with a zero-copy mapped initrd file. Prefer the builder.
@@ -1263,24 +1283,28 @@ impl Sandbox {
12631283 usbox. map_file_cow ( path, INITRD_MAP_BASE , Some ( "initrd" ) ) ?;
12641284 }
12651285
1266- let tools = build_tools ( tools, preopens) ?;
1267-
1268- // Register tool dispatch if needed
1269- if let Some ( tools) = tools {
1270- let tools = Arc :: new ( tools) ;
1271- let tools_ref = tools. clone ( ) ;
1272- usbox. register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1273- tools_ref. dispatch ( & payload)
1274- } ) ?;
1275- }
1286+ let exit_code = Arc :: new ( AtomicI32 :: new ( 0 ) ) ;
1287+ let mut tools = build_tools ( tools, preopens) ?. unwrap_or_default ( ) ;
1288+ register_internal_tools ( & mut tools, & exit_code) ;
1289+ let tools = Arc :: new ( tools) ;
1290+ let tools_ref = tools. clone ( ) ;
1291+ usbox. register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1292+ tools_ref. dispatch ( & payload)
1293+ } ) ?;
12761294
1277- Self :: finish_evolve ( usbox, initrd_path. map ( |p| p. to_path_buf ( ) ) , INITRD_MAP_BASE )
1295+ Self :: finish_evolve (
1296+ usbox,
1297+ initrd_path. map ( |p| p. to_path_buf ( ) ) ,
1298+ INITRD_MAP_BASE ,
1299+ exit_code,
1300+ )
12781301 }
12791302
12801303 fn finish_evolve (
12811304 usbox : UninitializedSandbox ,
12821305 file_mapping_path : Option < std:: path:: PathBuf > ,
12831306 file_mapping_base : u64 ,
1307+ exit_code : Arc < AtomicI32 > ,
12841308 ) -> Result < Self > {
12851309 let mut inner = usbox. evolve ( ) ?;
12861310 let snapshot = inner. snapshot ( ) . ok ( ) ;
@@ -1289,6 +1313,7 @@ impl Sandbox {
12891313 snapshot,
12901314 file_mapping_path,
12911315 file_mapping_base,
1316+ exit_code,
12921317 } )
12931318 }
12941319
@@ -1339,6 +1364,18 @@ impl Sandbox {
13391364 Ok ( self . inner . call ( func_name, args) ?)
13401365 }
13411366
1367+ /// Read the exit code reported by the guest via `__hl_exit`.
1368+ /// Defaults to 0 (success) if the guest never called it.
1369+ pub fn last_exit_code ( & self ) -> i32 {
1370+ self . exit_code . load ( Ordering :: Relaxed )
1371+ }
1372+
1373+ /// Reset the stored exit code to 0. Call before each guest
1374+ /// invocation so a previous non-zero code doesn't leak.
1375+ pub fn reset_exit_code ( & self ) {
1376+ self . exit_code . store ( 0 , Ordering :: Relaxed ) ;
1377+ }
1378+
13421379 /// Take a new snapshot of the current guest state.
13431380 ///
13441381 /// Useful for the "snapshot after one-time warm-up" pattern: call
@@ -1405,21 +1442,16 @@ impl Sandbox {
14051442 let loaded = Snapshot :: from_file_unchecked ( path. as_ref ( ) ) ?;
14061443 let arc = Arc :: new ( loaded) ;
14071444
1445+ let exit_code = Arc :: new ( AtomicI32 :: new ( 0 ) ) ;
1446+ let mut tools = build_tools ( None , preopens) ?. unwrap_or_default ( ) ;
1447+ register_internal_tools ( & mut tools, & exit_code) ;
1448+ let tools = Arc :: new ( tools) ;
1449+ let tools_ref = tools. clone ( ) ;
1450+
14081451 let mut host_funcs = HostFunctions :: default ( ) ;
1409- if !preopens. is_empty ( ) {
1410- if let Some ( tools) = build_tools ( None , preopens) ? {
1411- let tools = Arc :: new ( tools) ;
1412- let tools_ref = tools. clone ( ) ;
1413- host_funcs
1414- . register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1415- tools_ref. dispatch ( & payload)
1416- } ) ?;
1417- }
1418- } else {
1419- host_funcs. register_host_function ( "__dispatch" , |_payload : Vec < u8 > | -> Vec < u8 > {
1420- Vec :: new ( )
1421- } ) ?;
1422- }
1452+ host_funcs. register_host_function ( "__dispatch" , move |payload : Vec < u8 > | -> Vec < u8 > {
1453+ tools_ref. dispatch ( & payload)
1454+ } ) ?;
14231455
14241456 let inner = MultiUseSandbox :: from_snapshot ( arc. clone ( ) , host_funcs, None ) ?;
14251457
@@ -1428,6 +1460,7 @@ impl Sandbox {
14281460 snapshot : Some ( arc) ,
14291461 file_mapping_path : None ,
14301462 file_mapping_base : 0 ,
1463+ exit_code,
14311464 } )
14321465 }
14331466}
0 commit comments