@@ -808,24 +808,28 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
808808 Ok ( PathBuf :: from ( OsString :: from_vec ( buf) ) )
809809}
810810
811- fn open_and_set_permissions (
812- from : & Path ,
811+ fn open_from ( from : & Path ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: Metadata ) > {
812+ use crate :: fs:: File ;
813+
814+ let reader = File :: open ( from) ?;
815+ let metadata = reader. metadata ( ) ?;
816+ if !metadata. is_file ( ) {
817+ return Err ( Error :: new (
818+ ErrorKind :: InvalidInput ,
819+ "the source path is not an existing regular file" ,
820+ ) ) ;
821+ }
822+ Ok ( ( reader, metadata) )
823+ }
824+
825+ fn open_to_and_set_permissions (
813826 to : & Path ,
814- ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: File , u64 , crate :: fs:: Metadata ) > {
815- use crate :: fs:: { File , OpenOptions } ;
827+ reader_metadata : crate :: fs:: Metadata ,
828+ ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: Metadata ) > {
829+ use crate :: fs:: OpenOptions ;
816830 use crate :: os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
817831
818- let reader = File :: open ( from) ?;
819- let ( perm, len) = {
820- let metadata = reader. metadata ( ) ?;
821- if !metadata. is_file ( ) {
822- return Err ( Error :: new (
823- ErrorKind :: InvalidInput ,
824- "the source path is not an existing regular file" ,
825- ) ) ;
826- }
827- ( metadata. permissions ( ) , metadata. len ( ) )
828- } ;
832+ let perm = reader_metadata. permissions ( ) ;
829833 let writer = OpenOptions :: new ( )
830834 // create the file with the correct mode right away
831835 . mode ( perm. mode ( ) )
@@ -840,15 +844,16 @@ fn open_and_set_permissions(
840844 // pipes/FIFOs or device nodes.
841845 writer. set_permissions ( perm) ?;
842846 }
843- Ok ( ( reader , writer, len , writer_metadata) )
847+ Ok ( ( writer, writer_metadata) )
844848}
845849
846850#[ cfg( not( any( target_os = "linux" ,
847851 target_os = "android" ,
848852 target_os = "macos" ,
849853 target_os = "ios" ) ) ) ]
850854pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
851- let ( mut reader, mut writer, _, _) = open_and_set_permissions ( from, to) ?;
855+ let ( mut reader, reader_metadata) = open_from ( from) ?;
856+ let ( mut writer, _) = open_to_and_set_permissions ( to, reader_metadata) ?;
852857
853858 io:: copy ( & mut reader, & mut writer)
854859}
@@ -881,7 +886,9 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
881886 )
882887 }
883888
884- let ( mut reader, mut writer, len, _) = open_and_set_permissions ( from, to) ?;
889+ let ( mut reader, reader_metadata) = open_from ( from) ?;
890+ let len = reader_metadata. len ( ) ;
891+ let ( mut writer, _) = open_to_and_set_permissions ( to, reader_metadata) ?;
885892
886893 let has_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
887894 let mut written = 0u64 ;
@@ -940,6 +947,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
940947
941948#[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
942949pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
950+ use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
951+
943952 const COPYFILE_ACL : u32 = 1 << 0 ;
944953 const COPYFILE_STAT : u32 = 1 << 1 ;
945954 const COPYFILE_XATTR : u32 = 1 << 2 ;
@@ -985,7 +994,47 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
985994 }
986995 }
987996
988- let ( reader, writer, _, writer_metadata) = open_and_set_permissions ( from, to) ?;
997+ // MacOS prior to 10.12 don't support `fclonefileat`
998+ // We store the availability in a global to avoid unnecessary syscalls
999+ static HAS_FCLONEFILEAT : AtomicBool = AtomicBool :: new ( true ) ;
1000+ syscall ! {
1001+ fn fclonefileat(
1002+ srcfd: libc:: c_int,
1003+ dst_dirfd: libc:: c_int,
1004+ dst: * const libc:: c_char,
1005+ flags: libc:: c_int
1006+ ) -> libc:: c_int
1007+ }
1008+
1009+ let ( reader, reader_metadata) = open_from ( from) ?;
1010+
1011+ // Opportunistically attempt to create a copy-on-write clone of `from`
1012+ // using `fclonefileat`.
1013+ if HAS_FCLONEFILEAT . load ( Ordering :: Relaxed ) {
1014+ let clonefile_result = cvt ( unsafe {
1015+ fclonefileat (
1016+ reader. as_raw_fd ( ) ,
1017+ libc:: AT_FDCWD ,
1018+ cstr ( to) ?. as_ptr ( ) ,
1019+ 0 ,
1020+ )
1021+ } ) ;
1022+ match clonefile_result {
1023+ Ok ( _) => return Ok ( reader_metadata. len ( ) ) ,
1024+ Err ( err) => match err. raw_os_error ( ) {
1025+ // `fclonefileat` will fail on non-APFS volumes, if the
1026+ // destination already exists, or if the source and destination
1027+ // are on different devices. In all these cases `fcopyfile`
1028+ // should succeed.
1029+ Some ( libc:: ENOTSUP ) | Some ( libc:: EEXIST ) | Some ( libc:: EXDEV ) => ( ) ,
1030+ Some ( libc:: ENOSYS ) => HAS_FCLONEFILEAT . store ( false , Ordering :: Relaxed ) ,
1031+ _ => return Err ( err) ,
1032+ }
1033+ }
1034+ }
1035+
1036+ // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
1037+ let ( writer, writer_metadata) = open_to_and_set_permissions ( to, reader_metadata) ?;
9891038
9901039 // We ensure that `FreeOnDrop` never contains a null pointer so it is
9911040 // always safe to call `copyfile_state_free`
0 commit comments