@@ -175,6 +175,45 @@ 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 { 0 } else { raw:: UFFDIO_COPY_MODE_DONTWAKE } ,
200+ copy : 0 ,
201+ } ;
202+
203+ let _ =
204+ raw:: copy ( self . as_raw_fd ( ) , & mut copy as * mut raw:: uffdio_copy ) . map_err ( |errno| {
205+ match errno {
206+ Errno :: EAGAIN => Error :: PartiallyCopied ( copy. copy as usize ) ,
207+ _ => Error :: CopyFailed ( errno) ,
208+ }
209+ } ) ?;
210+ if copy. copy < 0 {
211+ Err ( Error :: CopyFailed ( Errno :: from_i32 ( -copy. copy as i32 ) ) )
212+ } else {
213+ Ok ( copy. copy as usize )
214+ }
215+ }
216+
178217 /// Zero out a memory address range registered with userfaultfd, and return the number of bytes
179218 /// that were successfully zeroed.
180219 ///
@@ -708,4 +747,108 @@ mod test {
708747
709748 Ok ( ( ) )
710749 }
750+
751+ /// Test that `copy_wp()` resolves a missing fault and applies write-protection in one ioctl.
752+ ///
753+ /// 1. Create a uffd registered for both MISSING and WRITE_PROTECT
754+ /// 2. Prepare a source page with value `42`
755+ /// 3. Spawn a thread that reads then writes the mapping
756+ /// 4. Handle the missing fault with `copy_wp()` — copies source data and sets WP in one ioctl
757+ /// 5. The thread's read succeeds (sees `42`), then the write triggers a WP fault
758+ /// 6. Handle the WP fault by removing write-protection
759+ /// 7. Verify the thread's write (`99`) landed
760+ #[ cfg( feature = "linux5_7" ) ]
761+ #[ test]
762+ fn test_copy_wp ( ) -> Result < ( ) > {
763+ const PAGE_SIZE : usize = 4096 ;
764+
765+ unsafe {
766+ let uffd = UffdBuilder :: new ( )
767+ . require_features ( FeatureFlags :: PAGEFAULT_FLAG_WP )
768+ . close_on_exec ( true )
769+ . create ( ) ?;
770+
771+ let mapping = libc:: mmap (
772+ ptr:: null_mut ( ) ,
773+ PAGE_SIZE ,
774+ libc:: PROT_READ | libc:: PROT_WRITE ,
775+ libc:: MAP_PRIVATE | libc:: MAP_ANON ,
776+ -1 ,
777+ 0 ,
778+ ) ;
779+
780+ assert ! ( !mapping. is_null( ) ) ;
781+
782+ assert ! ( uffd
783+ . register_with_mode(
784+ mapping,
785+ PAGE_SIZE ,
786+ RegisterMode :: MISSING | RegisterMode :: WRITE_PROTECT
787+ ) ?
788+ . contains( IoctlFlags :: WRITE_PROTECT ) ) ;
789+
790+ // Prepare a source page to copy from
791+ let src = libc:: mmap (
792+ ptr:: null_mut ( ) ,
793+ PAGE_SIZE ,
794+ libc:: PROT_READ | libc:: PROT_WRITE ,
795+ libc:: MAP_PRIVATE | libc:: MAP_ANON ,
796+ -1 ,
797+ 0 ,
798+ ) ;
799+ assert ! ( !src. is_null( ) ) ;
800+ * ( src as * mut u8 ) = 42 ;
801+
802+ let ptr = mapping as usize ;
803+ let thread = thread:: spawn ( move || {
804+ let ptr = ptr as * mut u8 ;
805+ // First access triggers missing fault; after copy_wp resolves it
806+ // with WP, this read succeeds but the subsequent write triggers
807+ // a write-protect fault.
808+ assert_eq ! ( * ptr, 42 ) ;
809+ * ptr = 99 ;
810+ } ) ;
811+
812+ loop {
813+ match uffd. read_event ( ) ? {
814+ Some ( Event :: Pagefault {
815+ kind,
816+ addr,
817+ ..
818+ } ) => 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 = uffd. copy_wp (
823+ src as * const c_void ,
824+ mapping,
825+ PAGE_SIZE ,
826+ true ,
827+ ) ?;
828+ assert_eq ! ( copied, PAGE_SIZE ) ;
829+ }
830+ FaultKind :: WriteProtected => {
831+ assert_eq ! ( addr, mapping) ;
832+ // Page should have the copied content
833+ assert_eq ! ( * ( addr as * const u8 ) , 42 ) ;
834+ uffd. remove_write_protection ( mapping, PAGE_SIZE , true ) ?;
835+ break ;
836+ }
837+ } ,
838+ _ => panic ! ( "unexpected event" ) ,
839+ }
840+ }
841+
842+ thread. join ( ) . expect ( "failed to join thread" ) ;
843+
844+ assert_eq ! ( * ( mapping as * const u8 ) , 99 ) ;
845+
846+ uffd. unregister ( mapping, PAGE_SIZE ) ?;
847+
848+ assert_eq ! ( libc:: munmap( mapping, PAGE_SIZE ) , 0 ) ;
849+ assert_eq ! ( libc:: munmap( src, PAGE_SIZE ) , 0 ) ;
850+ }
851+
852+ Ok ( ( ) )
853+ }
711854}
0 commit comments