@@ -175,6 +175,49 @@ impl Uffd {
175175 }
176176 }
177177
178+ /// Atomically copy a continuous memory chunk into the userfaultfd-registered range and
179+ /// register the pages as write-protected, returning the number of bytes successfully copied.
180+ ///
181+ /// This combines `UFFDIO_COPY` with `UFFDIO_COPY_MODE_WP` to resolve a page fault and
182+ /// enable write-protection tracking in a single ioctl.
183+ ///
184+ /// If `wake` is `true`, wake up the thread waiting for page fault resolution on the memory
185+ /// range.
186+ #[ cfg( feature = "linux5_7" ) ]
187+ pub unsafe fn copy_wp (
188+ & self ,
189+ src : * const c_void ,
190+ dst : * mut c_void ,
191+ len : usize ,
192+ wake : bool ,
193+ ) -> Result < usize > {
194+ let mut copy = raw:: uffdio_copy {
195+ src : src as u64 ,
196+ dst : dst as u64 ,
197+ len : len as u64 ,
198+ mode : raw:: UFFDIO_COPY_MODE_WP
199+ | if wake {
200+ 0
201+ } else {
202+ raw:: UFFDIO_COPY_MODE_DONTWAKE
203+ } ,
204+ copy : 0 ,
205+ } ;
206+
207+ let _ =
208+ raw:: copy ( self . as_raw_fd ( ) , & mut copy as * mut raw:: uffdio_copy ) . map_err ( |errno| {
209+ match errno {
210+ Errno :: EAGAIN => Error :: PartiallyCopied ( copy. copy as usize ) ,
211+ _ => Error :: CopyFailed ( errno) ,
212+ }
213+ } ) ?;
214+ if copy. copy < 0 {
215+ Err ( Error :: CopyFailed ( Errno :: from_i32 ( -copy. copy as i32 ) ) )
216+ } else {
217+ Ok ( copy. copy as usize )
218+ }
219+ }
220+
178221 /// Zero out a memory address range registered with userfaultfd, and return the number of bytes
179222 /// that were successfully zeroed.
180223 ///
@@ -708,4 +751,100 @@ mod test {
708751
709752 Ok ( ( ) )
710753 }
754+
755+ /// Test that `copy_wp()` resolves a missing fault and applies write-protection in one ioctl.
756+ ///
757+ /// 1. Create a uffd registered for both MISSING and WRITE_PROTECT
758+ /// 2. Prepare a source page with value `42`
759+ /// 3. Spawn a thread that reads then writes the mapping
760+ /// 4. Handle the missing fault with `copy_wp()` — copies source data and sets WP in one ioctl
761+ /// 5. The thread's read succeeds (sees `42`), then the write triggers a WP fault
762+ /// 6. Handle the WP fault by removing write-protection
763+ /// 7. Verify the thread's write (`99`) landed
764+ #[ cfg( feature = "linux5_7" ) ]
765+ #[ test]
766+ fn test_copy_wp ( ) -> Result < ( ) > {
767+ const PAGE_SIZE : usize = 4096 ;
768+
769+ unsafe {
770+ let uffd = UffdBuilder :: new ( )
771+ . require_features ( FeatureFlags :: PAGEFAULT_FLAG_WP )
772+ . close_on_exec ( true )
773+ . create ( ) ?;
774+
775+ let mapping = libc:: mmap (
776+ ptr:: null_mut ( ) ,
777+ PAGE_SIZE ,
778+ libc:: PROT_READ | libc:: PROT_WRITE ,
779+ libc:: MAP_PRIVATE | libc:: MAP_ANON ,
780+ -1 ,
781+ 0 ,
782+ ) ;
783+
784+ assert ! ( !mapping. is_null( ) ) ;
785+
786+ assert ! ( uffd
787+ . register_with_mode(
788+ mapping,
789+ PAGE_SIZE ,
790+ RegisterMode :: MISSING | RegisterMode :: WRITE_PROTECT
791+ ) ?
792+ . contains( IoctlFlags :: WRITE_PROTECT ) ) ;
793+
794+ // Prepare a source page to copy from
795+ let src = libc:: mmap (
796+ ptr:: null_mut ( ) ,
797+ PAGE_SIZE ,
798+ libc:: PROT_READ | libc:: PROT_WRITE ,
799+ libc:: MAP_PRIVATE | libc:: MAP_ANON ,
800+ -1 ,
801+ 0 ,
802+ ) ;
803+ assert ! ( !src. is_null( ) ) ;
804+ * ( src as * mut u8 ) = 42 ;
805+
806+ let ptr = mapping as usize ;
807+ let thread = thread:: spawn ( move || {
808+ let ptr = ptr as * mut u8 ;
809+ // First access triggers missing fault; after copy_wp resolves it
810+ // with WP, this read succeeds but the subsequent write triggers
811+ // a write-protect fault.
812+ assert_eq ! ( * ptr, 42 ) ;
813+ * ptr = 99 ;
814+ } ) ;
815+
816+ loop {
817+ match uffd. read_event ( ) ? {
818+ Some ( Event :: Pagefault { kind, addr, .. } ) => match kind {
819+ FaultKind :: Missing => {
820+ assert_eq ! ( addr, mapping) ;
821+ // Resolve the missing fault AND set write-protection in one call
822+ let copied =
823+ uffd. copy_wp ( src as * const c_void , mapping, PAGE_SIZE , true ) ?;
824+ assert_eq ! ( copied, PAGE_SIZE ) ;
825+ }
826+ FaultKind :: WriteProtected => {
827+ assert_eq ! ( addr, mapping) ;
828+ // Page should have the copied content
829+ assert_eq ! ( * ( addr as * const u8 ) , 42 ) ;
830+ uffd. remove_write_protection ( mapping, PAGE_SIZE , true ) ?;
831+ break ;
832+ }
833+ } ,
834+ _ => panic ! ( "unexpected event" ) ,
835+ }
836+ }
837+
838+ thread. join ( ) . expect ( "failed to join thread" ) ;
839+
840+ assert_eq ! ( * ( mapping as * const u8 ) , 99 ) ;
841+
842+ uffd. unregister ( mapping, PAGE_SIZE ) ?;
843+
844+ assert_eq ! ( libc:: munmap( mapping, PAGE_SIZE ) , 0 ) ;
845+ assert_eq ! ( libc:: munmap( src, PAGE_SIZE ) , 0 ) ;
846+ }
847+
848+ Ok ( ( ) )
849+ }
711850}
0 commit comments