@@ -827,30 +827,54 @@ pub fn canonicalize(p: &Path) -> io::Result<PathBuf> {
827827 Ok ( PathBuf :: from ( OsString :: from_vec ( buf) ) )
828828}
829829
830+ fn open_and_set_permissions (
831+ from : & Path ,
832+ to : & Path ,
833+ ) -> io:: Result < ( crate :: fs:: File , crate :: fs:: File , u64 , crate :: fs:: Metadata ) > {
834+ use crate :: fs:: { File , OpenOptions } ;
835+ use crate :: os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
836+
837+ let reader = File :: open ( from) ?;
838+ let ( perm, len) = {
839+ let metadata = reader. metadata ( ) ?;
840+ if !metadata. is_file ( ) {
841+ return Err ( Error :: new (
842+ ErrorKind :: InvalidInput ,
843+ "the source path is not an existing regular file" ,
844+ ) ) ;
845+ }
846+ ( metadata. permissions ( ) , metadata. len ( ) )
847+ } ;
848+ let writer = OpenOptions :: new ( )
849+ // create the file with the correct mode right away
850+ . mode ( perm. mode ( ) )
851+ . write ( true )
852+ . create ( true )
853+ . truncate ( true )
854+ . open ( to) ?;
855+ let writer_metadata = writer. metadata ( ) ?;
856+ if writer_metadata. is_file ( ) {
857+ // Set the correct file permissions, in case the file already existed.
858+ // Don't set the permissions on already existing non-files like
859+ // pipes/FIFOs or device nodes.
860+ writer. set_permissions ( perm) ?;
861+ }
862+ Ok ( ( reader, writer, len, writer_metadata) )
863+ }
864+
830865#[ cfg( not( any( target_os = "linux" ,
831866 target_os = "android" ,
832867 target_os = "macos" ,
833868 target_os = "ios" ) ) ) ]
834869pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
835- use crate :: fs:: File ;
836- if !from. is_file ( ) {
837- return Err ( Error :: new ( ErrorKind :: InvalidInput ,
838- "the source path is not an existing regular file" ) )
839- }
870+ let ( mut reader, mut writer, _, _) = open_and_set_permissions ( from, to) ?;
840871
841- let mut reader = File :: open ( from) ?;
842- let mut writer = File :: create ( to) ?;
843- let perm = reader. metadata ( ) ?. permissions ( ) ;
844-
845- let ret = io:: copy ( & mut reader, & mut writer) ?;
846- writer. set_permissions ( perm) ?;
847- Ok ( ret)
872+ io:: copy ( & mut reader, & mut writer)
848873}
849874
850875#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
851876pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
852877 use crate :: cmp;
853- use crate :: fs:: File ;
854878 use crate :: sync:: atomic:: { AtomicBool , Ordering } ;
855879
856880 // Kernel prior to 4.5 don't have copy_file_range
@@ -876,17 +900,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
876900 )
877901 }
878902
879- if !from. is_file ( ) {
880- return Err ( Error :: new ( ErrorKind :: InvalidInput ,
881- "the source path is not an existing regular file" ) )
882- }
883-
884- let mut reader = File :: open ( from) ?;
885- let mut writer = File :: create ( to) ?;
886- let ( perm, len) = {
887- let metadata = reader. metadata ( ) ?;
888- ( metadata. permissions ( ) , metadata. size ( ) )
889- } ;
903+ let ( mut reader, mut writer, len, _) = open_and_set_permissions ( from, to) ?;
890904
891905 let has_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
892906 let mut written = 0u64 ;
@@ -896,13 +910,14 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
896910 let copy_result = unsafe {
897911 // We actually don't have to adjust the offsets,
898912 // because copy_file_range adjusts the file offset automatically
899- cvt ( copy_file_range ( reader. as_raw_fd ( ) ,
900- ptr:: null_mut ( ) ,
901- writer. as_raw_fd ( ) ,
902- ptr:: null_mut ( ) ,
903- bytes_to_copy,
904- 0 )
905- )
913+ cvt ( copy_file_range (
914+ reader. as_raw_fd ( ) ,
915+ ptr:: null_mut ( ) ,
916+ writer. as_raw_fd ( ) ,
917+ ptr:: null_mut ( ) ,
918+ bytes_to_copy,
919+ 0 ,
920+ ) )
906921 } ;
907922 if let Err ( ref copy_err) = copy_result {
908923 match copy_err. raw_os_error ( ) {
@@ -920,24 +935,25 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
920935 Ok ( ret) => written += ret as u64 ,
921936 Err ( err) => {
922937 match err. raw_os_error ( ) {
923- Some ( os_err) if os_err == libc:: ENOSYS
924- || os_err == libc:: EXDEV
925- || os_err == libc:: EPERM => {
926- // Try fallback io::copy if either:
927- // - Kernel version is < 4.5 (ENOSYS)
928- // - Files are mounted on different fs (EXDEV)
929- // - copy_file_range is disallowed, for example by seccomp (EPERM)
930- assert_eq ! ( written, 0 ) ;
931- let ret = io:: copy ( & mut reader, & mut writer) ?;
932- writer. set_permissions ( perm) ?;
933- return Ok ( ret)
934- } ,
938+ Some ( os_err)
939+ if os_err == libc:: ENOSYS
940+ || os_err == libc:: EXDEV
941+ || os_err == libc:: EINVAL
942+ || os_err == libc:: EPERM =>
943+ {
944+ // Try fallback io::copy if either:
945+ // - Kernel version is < 4.5 (ENOSYS)
946+ // - Files are mounted on different fs (EXDEV)
947+ // - copy_file_range is disallowed, for example by seccomp (EPERM)
948+ // - copy_file_range cannot be used with pipes or device nodes (EINVAL)
949+ assert_eq ! ( written, 0 ) ;
950+ return io:: copy ( & mut reader, & mut writer) ;
951+ }
935952 _ => return Err ( err) ,
936953 }
937954 }
938955 }
939956 }
940- writer. set_permissions ( perm) ?;
941957 Ok ( written)
942958}
943959
@@ -960,9 +976,9 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
960976 type copyfile_flags_t = u32 ;
961977
962978 extern "C" {
963- fn copyfile (
964- from : * const libc:: c_char ,
965- to : * const libc:: c_char ,
979+ fn fcopyfile (
980+ from : libc:: c_int ,
981+ to : libc:: c_int ,
966982 state : copyfile_state_t ,
967983 flags : copyfile_flags_t ,
968984 ) -> libc:: c_int ;
@@ -988,10 +1004,7 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
9881004 }
9891005 }
9901006
991- if !from. is_file ( ) {
992- return Err ( Error :: new ( ErrorKind :: InvalidInput ,
993- "the source path is not an existing regular file" ) )
994- }
1007+ let ( reader, writer, _, writer_metadata) = open_and_set_permissions ( from, to) ?;
9951008
9961009 // We ensure that `FreeOnDrop` never contains a null pointer so it is
9971010 // always safe to call `copyfile_state_free`
@@ -1003,12 +1016,18 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
10031016 FreeOnDrop ( state)
10041017 } ;
10051018
1019+ let flags = if writer_metadata. is_file ( ) {
1020+ COPYFILE_ALL
1021+ } else {
1022+ COPYFILE_DATA
1023+ } ;
1024+
10061025 cvt ( unsafe {
1007- copyfile (
1008- cstr ( from ) ? . as_ptr ( ) ,
1009- cstr ( to ) ? . as_ptr ( ) ,
1026+ fcopyfile (
1027+ reader . as_raw_fd ( ) ,
1028+ writer . as_raw_fd ( ) ,
10101029 state. 0 ,
1011- COPYFILE_ALL ,
1030+ flags ,
10121031 )
10131032 } ) ?;
10141033
0 commit comments