@@ -823,24 +823,28 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
823823 Ok ( PathBuf :: from ( OsString :: from_vec ( buf) ) )
824824}
825825
826- fn open_and_set_permissions (
827- from : & Path ,
826+ fn open_from ( from : & Path ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: Metadata ) > {
827+ use crate :: fs:: File ;
828+
829+ let reader = File :: open ( from) ?;
830+ let metadata = reader. metadata ( ) ?;
831+ if !metadata. is_file ( ) {
832+ return Err ( Error :: new (
833+ ErrorKind :: InvalidInput ,
834+ "the source path is not an existing regular file" ,
835+ ) ) ;
836+ }
837+ Ok ( ( reader, metadata) )
838+ }
839+
840+ fn open_to_and_set_permissions (
828841 to : & Path ,
829- ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: File , u64 , crate :: fs:: Metadata ) > {
830- use crate :: fs:: { File , OpenOptions } ;
842+ reader_metadata : crate :: fs:: Metadata ,
843+ ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: Metadata ) > {
844+ use crate :: fs:: OpenOptions ;
831845 use crate :: os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
832846
833- let reader = File :: open ( from) ?;
834- let ( perm, len) = {
835- let metadata = reader. metadata ( ) ?;
836- if !metadata. is_file ( ) {
837- return Err ( Error :: new (
838- ErrorKind :: InvalidInput ,
839- "the source path is not an existing regular file" ,
840- ) ) ;
841- }
842- ( metadata. permissions ( ) , metadata. len ( ) )
843- } ;
847+ let perm = reader_metadata. permissions ( ) ;
844848 let writer = OpenOptions :: new ( )
845849 // create the file with the correct mode right away
846850 . mode ( perm. mode ( ) )
@@ -855,15 +859,16 @@ fn open_and_set_permissions(
855859 // pipes/FIFOs or device nodes.
856860 writer. set_permissions ( perm) ?;
857861 }
858- Ok ( ( reader , writer, len , writer_metadata) )
862+ Ok ( ( writer, writer_metadata) )
859863}
860864
861865#[ cfg( not( any( target_os = "linux" ,
862866 target_os = "android" ,
863867 target_os = "macos" ,
864868 target_os = "ios" ) ) ) ]
865869pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
866- let ( mut reader, mut writer, _, _) = open_and_set_permissions ( from, to) ?;
870+ let ( mut reader, reader_metadata) = open_from ( from) ?;
871+ let ( mut writer, _) = open_to_and_set_permissions ( to, reader_metadata) ?;
867872
868873 io:: copy ( & mut reader, & mut writer)
869874}
@@ -896,7 +901,9 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
896901 )
897902 }
898903
899- let ( mut reader, mut writer, len, _) = open_and_set_permissions ( from, to) ?;
904+ let ( mut reader, reader_metadata) = open_from ( from) ?;
905+ let len = reader_metadata. len ( ) ;
906+ let ( mut writer, _) = open_to_and_set_permissions ( to, reader_metadata) ?;
900907
901908 let has_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
902909 let mut written = 0u64 ;
@@ -955,6 +962,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
955962
956963#[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
957964pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
965+ use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
966+
958967 const COPYFILE_ACL : u32 = 1 << 0 ;
959968 const COPYFILE_STAT : u32 = 1 << 1 ;
960969 const COPYFILE_XATTR : u32 = 1 << 2 ;
@@ -1000,7 +1009,48 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
10001009 }
10011010 }
10021011
1003- let ( reader, writer, _, writer_metadata) = open_and_set_permissions ( from, to) ?;
1012+ // MacOS prior to 10.12 don't support `fclonefileat`
1013+ // We store the availability in a global to avoid unnecessary syscalls
1014+ static HAS_FCLONEFILEAT : AtomicBool = AtomicBool :: new ( true ) ;
1015+ syscall ! {
1016+ fn fclonefileat(
1017+ srcfd: libc:: c_int,
1018+ dst_dirfd: libc:: c_int,
1019+ dst: * const libc:: c_char,
1020+ flags: libc:: c_int
1021+ ) -> libc:: c_int
1022+ }
1023+
1024+ let ( reader, reader_metadata) = open_from ( from) ?;
1025+
1026+ // Opportunistically attempt to create a copy-on-write clone of `from`
1027+ // using `fclonefileat`.
1028+ if HAS_FCLONEFILEAT . load ( Ordering :: Relaxed ) {
1029+ let to = cstr ( to) ?;
1030+ let clonefile_result = cvt ( unsafe {
1031+ fclonefileat (
1032+ reader. as_raw_fd ( ) ,
1033+ libc:: AT_FDCWD ,
1034+ to. as_ptr ( ) ,
1035+ 0 ,
1036+ )
1037+ } ) ;
1038+ match clonefile_result {
1039+ Ok ( _) => return Ok ( reader_metadata. len ( ) ) ,
1040+ Err ( err) => match err. raw_os_error ( ) {
1041+ // `fclonefileat` will fail on non-APFS volumes, if the
1042+ // destination already exists, or if the source and destination
1043+ // are on different devices. In all these cases `fcopyfile`
1044+ // should succeed.
1045+ Some ( libc:: ENOTSUP ) | Some ( libc:: EEXIST ) | Some ( libc:: EXDEV ) => ( ) ,
1046+ Some ( libc:: ENOSYS ) => HAS_FCLONEFILEAT . store ( false , Ordering :: Relaxed ) ,
1047+ _ => return Err ( err) ,
1048+ }
1049+ }
1050+ }
1051+
1052+ // Fall back to using `fcopyfile` if `fclonefileat` does not succeed.
1053+ let ( writer, writer_metadata) = open_to_and_set_permissions ( to, reader_metadata) ?;
10041054
10051055 // We ensure that `FreeOnDrop` never contains a null pointer so it is
10061056 // always safe to call `copyfile_state_free`
0 commit comments