@@ -808,24 +808,28 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
808
808
Ok ( PathBuf :: from ( OsString :: from_vec ( buf) ) )
809
809
}
810
810
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 (
813
826
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 ;
816
830
use crate :: os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
817
831
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 ( ) ;
829
833
let writer = OpenOptions :: new ( )
830
834
// create the file with the correct mode right away
831
835
. mode ( perm. mode ( ) )
@@ -840,15 +844,16 @@ fn open_and_set_permissions(
840
844
// pipes/FIFOs or device nodes.
841
845
writer. set_permissions ( perm) ?;
842
846
}
843
- Ok ( ( reader , writer, len , writer_metadata) )
847
+ Ok ( ( writer, writer_metadata) )
844
848
}
845
849
846
850
#[ cfg( not( any( target_os = "linux" ,
847
851
target_os = "android" ,
848
852
target_os = "macos" ,
849
853
target_os = "ios" ) ) ) ]
850
854
pub 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) ?;
852
857
853
858
io:: copy ( & mut reader, & mut writer)
854
859
}
@@ -881,7 +886,9 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
881
886
)
882
887
}
883
888
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) ?;
885
892
886
893
let has_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
887
894
let mut written = 0u64 ;
@@ -940,6 +947,8 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
940
947
941
948
#[ cfg( any( target_os = "macos" , target_os = "ios" ) ) ]
942
949
pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
950
+ use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
951
+
943
952
const COPYFILE_ACL : u32 = 1 << 0 ;
944
953
const COPYFILE_STAT : u32 = 1 << 1 ;
945
954
const COPYFILE_XATTR : u32 = 1 << 2 ;
@@ -985,7 +994,47 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
985
994
}
986
995
}
987
996
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) ?;
989
1038
990
1039
// We ensure that `FreeOnDrop` never contains a null pointer so it is
991
1040
// always safe to call `copyfile_state_free`
0 commit comments