@@ -2029,6 +2029,221 @@ impl ReadonlySharedMemory {
20292029 } )
20302030 }
20312031
2032+ /// Create a `ReadonlySharedMemory` backed by a file on disk.
2033+ ///
2034+ /// The memory blob at `[offset..offset+len)` in the file is mapped
2035+ /// with copy-on-write semantics so that guest writes trigger
2036+ /// per-page CoW faults without modifying the backing file.
2037+ ///
2038+ /// The mapping is surrounded by guard pages, matching the
2039+ /// `ExclusiveSharedMemory::new` layout, so that `base_ptr()` and
2040+ /// `mem_size()` return the correct values via the `SharedMemory`
2041+ /// trait.
2042+ ///
2043+ /// - **Linux**: `mmap(MAP_PRIVATE)` for zero-copy file-backed CoW.
2044+ /// - **Windows**: `CreateFileMappingA(PAGE_WRITECOPY)` +
2045+ /// `MapViewOfFile(FILE_MAP_COPY)` for zero-copy file-backed CoW.
2046+ /// The returned `HostMapping` carries the file mapping handle so
2047+ /// the surrogate process can create its own view via
2048+ /// `MapViewOfFileNuma2`.
2049+ pub ( crate ) fn from_file ( file : & std:: fs:: File , offset : u64 , len : usize ) -> Result < Self > {
2050+ if len == 0 {
2051+ return Err ( new_error ! (
2052+ "Cannot create file-backed shared memory with size 0"
2053+ ) ) ;
2054+ }
2055+
2056+ let total_size = len. checked_add ( 2 * PAGE_SIZE_USIZE ) . ok_or_else ( || {
2057+ new_error ! ( "Memory required for file-backed snapshot exceeded usize::MAX" )
2058+ } ) ?;
2059+
2060+ #[ cfg( target_os = "linux" ) ]
2061+ {
2062+ Self :: from_file_linux ( file, offset, len, total_size)
2063+ }
2064+ #[ cfg( target_os = "windows" ) ]
2065+ {
2066+ // The Windows path maps the entire file from offset 0 and uses
2067+ // the header page as the leading guard page. This requires the
2068+ // memory blob to start at exactly PAGE_SIZE.
2069+ if offset as usize != PAGE_SIZE_USIZE {
2070+ return Err ( new_error ! (
2071+ "Windows from_file requires offset == PAGE_SIZE, got {}" ,
2072+ offset
2073+ ) ) ;
2074+ }
2075+ Self :: from_file_windows ( file, total_size)
2076+ }
2077+ }
2078+
2079+ #[ cfg( target_os = "linux" ) ]
2080+ fn from_file_linux (
2081+ file : & std:: fs:: File ,
2082+ offset : u64 ,
2083+ len : usize ,
2084+ total_size : usize ,
2085+ ) -> Result < Self > {
2086+ use std:: ffi:: c_void;
2087+ use std:: os:: unix:: io:: AsRawFd ;
2088+
2089+ use libc:: {
2090+ MAP_ANONYMOUS , MAP_FAILED , MAP_FIXED , MAP_NORESERVE , MAP_PRIVATE , PROT_NONE , PROT_READ ,
2091+ PROT_WRITE , mmap, off_t, size_t,
2092+ } ;
2093+
2094+ let fd = file. as_raw_fd ( ) ;
2095+ let offset: off_t = offset
2096+ . try_into ( )
2097+ . map_err ( |_| new_error ! ( "snapshot file offset {} exceeds off_t range" , offset) ) ?;
2098+
2099+ // Allocate the full region (guard + usable + guard) as anonymous
2100+ let base = unsafe {
2101+ mmap (
2102+ null_mut ( ) ,
2103+ total_size as size_t ,
2104+ PROT_NONE ,
2105+ MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE ,
2106+ -1 ,
2107+ 0 as off_t ,
2108+ )
2109+ } ;
2110+ if base == MAP_FAILED {
2111+ return Err ( HyperlightError :: MmapFailed (
2112+ std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) ,
2113+ ) ) ;
2114+ }
2115+
2116+ // Map the file content over the usable portion (between guard pages).
2117+ // PROT_READ | PROT_WRITE: KVM/MSHV require writable host mappings
2118+ // to handle copy-on-write page faults from the guest.
2119+ // MAP_PRIVATE: writes go to private copies, not the file.
2120+ let usable_ptr = unsafe { ( base as * mut u8 ) . add ( PAGE_SIZE_USIZE ) } ;
2121+ let mapped = unsafe {
2122+ mmap (
2123+ usable_ptr as * mut c_void ,
2124+ len as size_t ,
2125+ PROT_READ | PROT_WRITE ,
2126+ MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE ,
2127+ fd,
2128+ offset,
2129+ )
2130+ } ;
2131+ if mapped == MAP_FAILED {
2132+ unsafe { libc:: munmap ( base, total_size as size_t ) } ;
2133+ return Err ( HyperlightError :: MmapFailed (
2134+ std:: io:: Error :: last_os_error ( ) . raw_os_error ( ) ,
2135+ ) ) ;
2136+ }
2137+
2138+ // Guard pages at base and base+total_size-PAGE_SIZE are already
2139+ // PROT_NONE from the anonymous mapping; MAP_FIXED only replaced
2140+ // the middle portion.
2141+
2142+ #[ allow( clippy:: arc_with_non_send_sync) ]
2143+ Ok ( ReadonlySharedMemory {
2144+ region : Arc :: new ( HostMapping {
2145+ ptr : base as * mut u8 ,
2146+ size : total_size,
2147+ } ) ,
2148+ } )
2149+ }
2150+
2151+ /// Windows implementation of file-backed read-only shared memory.
2152+ ///
2153+ /// The snapshot file layout is:
2154+ /// `[header (PAGE_SIZE)][memory blob][trailing padding (PAGE_SIZE)]`.
2155+ /// We create a read-only file mapping covering the entire file and
2156+ /// map a view of `len + 2*PAGE_SIZE` bytes starting at file offset 0.
2157+ /// The header becomes the leading guard page and the trailing padding
2158+ /// becomes the trailing guard page, both via
2159+ /// `VirtualProtect(PAGE_NOACCESS)`. This gives the standard
2160+ /// `HostMapping` layout: `[guard | usable | guard]`.
2161+ #[ cfg( target_os = "windows" ) ]
2162+ fn from_file_windows ( file : & std:: fs:: File , total_size : usize ) -> Result < Self > {
2163+ use std:: os:: windows:: io:: AsRawHandle ;
2164+
2165+ use windows:: Win32 :: Foundation :: HANDLE ;
2166+ use windows:: Win32 :: System :: Memory :: {
2167+ CreateFileMappingA , FILE_MAP_READ , MapViewOfFile , PAGE_NOACCESS , PAGE_PROTECTION_FLAGS ,
2168+ PAGE_READONLY , VirtualProtect ,
2169+ } ;
2170+ use windows:: core:: PCSTR ;
2171+
2172+ let file_handle = HANDLE ( file. as_raw_handle ( ) ) ;
2173+
2174+ // Create a read-only file mapping at the exact file size (pass 0,0).
2175+ // The file includes trailing PAGE_SIZE padding written by to_file(),
2176+ // so the file is at least offset + len + PAGE_SIZE = total_size bytes.
2177+ let handle =
2178+ unsafe { CreateFileMappingA ( file_handle, None , PAGE_READONLY , 0 , 0 , PCSTR :: null ( ) ) ? } ;
2179+
2180+ if handle. is_invalid ( ) {
2181+ log_then_return ! ( HyperlightError :: MemoryAllocationFailed (
2182+ Error :: last_os_error( ) . raw_os_error( )
2183+ ) ) ;
2184+ }
2185+
2186+ // Map exactly total_size (header + blob + trailing padding) bytes.
2187+ let addr = unsafe { MapViewOfFile ( handle, FILE_MAP_READ , 0 , 0 , total_size) } ;
2188+ if addr. Value . is_null ( ) {
2189+ unsafe {
2190+ let _ = windows:: Win32 :: Foundation :: CloseHandle ( handle) ;
2191+ }
2192+ log_then_return ! ( HyperlightError :: MemoryAllocationFailed (
2193+ Error :: last_os_error( ) . raw_os_error( )
2194+ ) ) ;
2195+ }
2196+
2197+ let cleanup = |ptr : * mut c_void , handle : windows:: Win32 :: Foundation :: HANDLE | unsafe {
2198+ if let Err ( e) = windows:: Win32 :: System :: Memory :: UnmapViewOfFile (
2199+ windows:: Win32 :: System :: Memory :: MEMORY_MAPPED_VIEW_ADDRESS { Value : ptr } ,
2200+ ) {
2201+ tracing:: error!( "from_file_windows cleanup: UnmapViewOfFile failed: {:?}" , e) ;
2202+ }
2203+ if let Err ( e) = windows:: Win32 :: Foundation :: CloseHandle ( handle) {
2204+ tracing:: error!( "from_file_windows cleanup: CloseHandle failed: {:?}" , e) ;
2205+ }
2206+ } ;
2207+
2208+ // Set guard pages on both ends.
2209+ let mut unused_old_prot = PAGE_PROTECTION_FLAGS ( 0 ) ;
2210+
2211+ let first_guard = addr. Value ;
2212+ if let Err ( e) = unsafe {
2213+ VirtualProtect (
2214+ first_guard,
2215+ PAGE_SIZE_USIZE ,
2216+ PAGE_NOACCESS ,
2217+ & mut unused_old_prot,
2218+ )
2219+ } {
2220+ cleanup ( addr. Value , handle) ;
2221+ log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
2222+ }
2223+
2224+ let last_guard = unsafe { first_guard. add ( total_size - PAGE_SIZE_USIZE ) } ;
2225+ if let Err ( e) = unsafe {
2226+ VirtualProtect (
2227+ last_guard,
2228+ PAGE_SIZE_USIZE ,
2229+ PAGE_NOACCESS ,
2230+ & mut unused_old_prot,
2231+ )
2232+ } {
2233+ cleanup ( addr. Value , handle) ;
2234+ log_then_return ! ( WindowsAPIError ( e. clone( ) ) ) ;
2235+ }
2236+
2237+ #[ allow( clippy:: arc_with_non_send_sync) ]
2238+ Ok ( ReadonlySharedMemory {
2239+ region : Arc :: new ( HostMapping {
2240+ ptr : addr. Value as * mut u8 ,
2241+ size : total_size,
2242+ handle,
2243+ } ) ,
2244+ } )
2245+ }
2246+
20322247 pub ( crate ) fn as_slice ( & self ) -> & [ u8 ] {
20332248 unsafe { std:: slice:: from_raw_parts ( self . base_ptr ( ) , self . mem_size ( ) ) }
20342249 }
0 commit comments