99
1010use std:: cell:: Cell ;
1111use std:: os:: fd:: { AsRawFd as _, FromRawFd as _} ;
12+ use std:: path:: PathBuf ;
1213use std:: sync:: atomic:: { AtomicI32 , AtomicU32 , Ordering } ;
1314use std:: time:: Duration ;
1415
1516use litebox:: fs:: OFlags ;
1617use litebox:: platform:: UnblockedOrTimedOut ;
17- use litebox:: platform:: page_mgmt:: { FixedAddressBehavior , MemoryRegionPermissions } ;
18+ use litebox:: platform:: page_mgmt:: {
19+ CowAllocationError , FixedAddressBehavior , MemoryRegionPermissions ,
20+ } ;
1821use litebox:: platform:: { ImmediatelyWokenUp , RawConstPointer as _} ;
1922use litebox:: shim:: ContinueOperation ;
2023use litebox:: utils:: { ReinterpretSignedExt , ReinterpretUnsignedExt as _, TruncateExt } ;
@@ -40,6 +43,9 @@ pub struct LinuxUserland {
4043 reserved_pages : Vec < core:: ops:: Range < usize > > ,
4144 /// The base address of the VDSO.
4245 vdso_address : Option < usize > ,
46+ /// CoW-eligible memory regions. Maps start address of the static slice, to the info needed to
47+ /// re-mmap the file.
48+ cow_regions : std:: sync:: RwLock < std:: collections:: BTreeMap < usize , CowRegionInfo > > ,
4349}
4450
4551impl core:: fmt:: Debug for LinuxUserland {
@@ -48,6 +54,15 @@ impl core::fmt::Debug for LinuxUserland {
4854 }
4955}
5056
57+ /// Information about a CoW-eligible memory region backed by a file.
58+ #[ derive( Debug , Clone ) ]
59+ struct CowRegionInfo {
60+ /// The path to the backing file on the host filesystem.
61+ file_path : PathBuf ,
62+ /// Length of the backing file.
63+ file_length : usize ,
64+ }
65+
5166const IF_NAMESIZE : usize = 16 ;
5267/// Use TUN device
5368const IFF_TUN : i32 = 0x0001 ;
@@ -163,10 +178,53 @@ impl LinuxUserland {
163178 seccomp_interception_enabled : std:: sync:: atomic:: AtomicBool :: new ( false ) ,
164179 reserved_pages,
165180 vdso_address,
181+ cow_regions : std:: sync:: RwLock :: new ( std:: collections:: BTreeMap :: new ( ) ) ,
166182 } ;
167183 Box :: leak ( Box :: new ( platform) )
168184 }
169185
186+ /// Register a CoW-eligible memory region backed by a file.
187+ ///
188+ /// # Panics
189+ ///
190+ /// Panics if an overlapping region is already registered.
191+ pub fn register_cow_region ( & self , data : & ' static [ u8 ] , file_path : impl Into < PathBuf > ) {
192+ let start = data. as_ptr ( ) as usize ;
193+ let info = CowRegionInfo {
194+ file_path : file_path. into ( ) ,
195+ file_length : data. len ( ) ,
196+ } ;
197+
198+ let mut regions = self . cow_regions . write ( ) . unwrap ( ) ;
199+ assert ! (
200+ regions. range( start..start + data. len( ) ) . next( ) . is_none( ) ,
201+ "Attempting to register an overlapping region"
202+ ) ;
203+ let old = regions. insert ( start, info) ;
204+ assert ! ( old. is_none( ) ) ;
205+ }
206+
207+ /// Look up the file backing a static slice for CoW mapping.
208+ ///
209+ /// Returns `Some((file_path, offset_in_file))` if the slice is backed by a registered
210+ /// CoW region, `None` otherwise.
211+ fn lookup_cow_region ( & self , source_data : & ' static [ u8 ] ) -> Option < ( PathBuf , usize ) > {
212+ let slice_start = source_data. as_ptr ( ) as usize ;
213+ let slice_len = source_data. len ( ) ;
214+
215+ let regions = self . cow_regions . read ( ) . unwrap ( ) ;
216+
217+ if let Some ( ( & region_start, info) ) = regions. range ( ..=slice_start) . next_back ( ) {
218+ let region_end = region_start. checked_add ( info. file_length ) . unwrap ( ) ;
219+ let slice_end = slice_start. checked_add ( slice_len) . unwrap ( ) ;
220+
221+ if slice_start >= region_start && slice_end <= region_end {
222+ return Some ( ( info. file_path . clone ( ) , slice_start - region_start) ) ;
223+ }
224+ }
225+ None
226+ }
227+
170228 /// Enable seccomp syscall interception on the platform.
171229 ///
172230 /// # Panics
@@ -1490,6 +1548,89 @@ impl<const ALIGN: usize> litebox::platform::PageManagementProvider<ALIGN> for Li
14901548 fn reserved_pages ( & self ) -> impl Iterator < Item = & core:: ops:: Range < usize > > {
14911549 self . reserved_pages . iter ( )
14921550 }
1551+
1552+ fn try_allocate_cow_pages (
1553+ & self ,
1554+ suggested_start : usize ,
1555+ source_data : & ' static [ u8 ] ,
1556+ permissions : MemoryRegionPermissions ,
1557+ fixed_address_behavior : FixedAddressBehavior ,
1558+ ) -> Result < Self :: RawMutPointer < u8 > , CowAllocationError > {
1559+ let Some ( ( file_path, file_offset) ) = self . lookup_cow_region ( source_data) else {
1560+ return Err ( CowAllocationError :: UnsupportedSourceRegion ) ;
1561+ } ;
1562+ if !file_offset. is_multiple_of ( ALIGN ) {
1563+ return Err ( CowAllocationError :: Unaligned ) ;
1564+ }
1565+
1566+ let file_path_cstr =
1567+ std:: ffi:: CString :: new ( file_path. as_os_str ( ) . as_encoded_bytes ( ) ) . unwrap ( ) ;
1568+ // TODO(jb): We should likely be storing pre-opened FDs, right?
1569+ let fd = unsafe {
1570+ syscalls:: syscall4 (
1571+ syscalls:: Sysno :: open,
1572+ file_path_cstr. as_ptr ( ) as usize ,
1573+ OFlags :: RDONLY . bits ( ) as usize ,
1574+ 0 ,
1575+ // Unused by the syscall but would be checked by Seccomp filter if enabled.
1576+ syscall_intercept:: SYSCALL_ARG_MAGIC ,
1577+ )
1578+ } ;
1579+ let fd = fd. expect ( "file should remain unchanged on host" ) ;
1580+
1581+ let mut flags = MapFlags :: MAP_PRIVATE ;
1582+ match fixed_address_behavior {
1583+ FixedAddressBehavior :: Hint => { }
1584+ FixedAddressBehavior :: Replace => flags |= MapFlags :: MAP_FIXED ,
1585+ FixedAddressBehavior :: NoReplace => flags |= MapFlags :: MAP_FIXED_NOREPLACE ,
1586+ }
1587+
1588+ let result = unsafe {
1589+ syscalls:: syscall6 (
1590+ {
1591+ #[ cfg( target_arch = "x86_64" ) ]
1592+ {
1593+ syscalls:: Sysno :: mmap
1594+ }
1595+ #[ cfg( target_arch = "x86" ) ]
1596+ {
1597+ syscalls:: Sysno :: mmap2
1598+ }
1599+ } ,
1600+ suggested_start,
1601+ source_data. len ( ) ,
1602+ prot_flags ( permissions) . bits ( ) . reinterpret_as_unsigned ( ) as usize ,
1603+ ( flags. bits ( ) . reinterpret_as_unsigned ( )
1604+ // This is to ensure it won't be intercepted by Seccomp if enabled.
1605+ | syscall_intercept:: MMAP_FLAG_MAGIC ) as usize ,
1606+ fd,
1607+ {
1608+ #[ cfg( target_arch = "x86_64" ) ]
1609+ {
1610+ file_offset
1611+ }
1612+ #[ cfg( target_arch = "x86" ) ]
1613+ {
1614+ // mmap2 takes offset in pages, not bytes
1615+ file_offset / ALIGN
1616+ }
1617+ } ,
1618+ )
1619+ } ;
1620+
1621+ let _ = unsafe {
1622+ syscalls:: syscall2 (
1623+ syscalls:: Sysno :: close,
1624+ fd, // This is to ensure it won't be intercepted by Seccomp if enabled.
1625+ syscall_intercept:: SYSCALL_ARG_MAGIC ,
1626+ )
1627+ } ;
1628+
1629+ match result {
1630+ Ok ( ptr) => Ok ( UserMutPtr :: from_usize ( ptr) ) ,
1631+ Err ( _) => Err ( CowAllocationError :: InternalFailure ) ,
1632+ }
1633+ }
14931634}
14941635
14951636impl litebox:: platform:: StdioProvider for LinuxUserland {
0 commit comments