@@ -843,12 +843,16 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
843843#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
844844pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
845845 use cmp;
846- use fs:: File ;
846+ use io:: { Read , Write } ;
847+ use os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
848+ use fs:: { File , OpenOptions } ;
847849 use sync:: atomic:: { AtomicBool , Ordering } ;
848-
849850 // Kernel prior to 4.5 don't have copy_file_range
850851 // We store the availability in a global to avoid unnecessary syscalls
851852 static HAS_COPY_FILE_RANGE : AtomicBool = AtomicBool :: new ( true ) ;
853+ // Kernel prior to 2.2 don't have sendfile
854+ // We store the availability in a global to avoid unnecessary syscalls
855+ static HAS_SENDFILE : AtomicBool = AtomicBool :: new ( true ) ;
852856
853857 unsafe fn copy_file_range (
854858 fd_in : libc:: c_int ,
@@ -869,67 +873,193 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
869873 )
870874 }
871875
872- if !from. is_file ( ) {
873- return Err ( Error :: new ( ErrorKind :: InvalidInput ,
874- "the source path is not an existing regular file" ) )
875- }
876-
877876 let mut reader = File :: open ( from) ?;
878- let mut writer = File :: create ( to ) ? ;
879- let ( perm , len) = {
877+
878+ let ( mode , len) = {
880879 let metadata = reader. metadata ( ) ?;
881- ( metadata. permissions ( ) , metadata. size ( ) )
880+ if !metadata. is_file ( ) {
881+ return Err ( Error :: new (
882+ ErrorKind :: InvalidInput ,
883+ "the source path is not an existing regular file" ,
884+ ) ) ;
885+ }
886+ ( metadata. permissions ( ) . mode ( ) , metadata. len ( ) )
882887 } ;
888+ let bytes_to_copy: i64 = len as i64 ;
889+
890+ let mut writer = OpenOptions :: new ( )
891+ // prevent world readable/writeable file in case of empty umask
892+ . mode ( 0o000 )
893+ . write ( true )
894+ . create ( true )
895+ . truncate ( true )
896+ . open ( to) ?;
897+
898+ let mut can_handle_sparse = true ;
899+
900+ let fd_in = reader. as_raw_fd ( ) ;
901+ let fd_out = writer. as_raw_fd ( ) ;
902+
903+ let writer_metadata = writer. metadata ( ) ?;
904+ // prevent root from setting permissions on e.g. `/dev/null`
905+ // prevent users from setting permissions on e.g. `/dev/stdout` or a named pipe
906+ if writer_metadata. is_file ( ) {
907+ // set the correct file mode
908+ cvt_r ( || unsafe { libc:: fchmod ( fd_out, mode) } ) ?;
909+ match cvt_r ( || unsafe { ftruncate64 ( fd_out, bytes_to_copy) } ) {
910+ Ok ( _) => { }
911+ Err ( err) => match err. raw_os_error ( ) {
912+ Some ( libc:: EINVAL ) => {
913+ can_handle_sparse = false ;
914+ }
915+ _ => {
916+ return Err ( err) ;
917+ }
918+ } ,
919+ }
920+ } else {
921+ can_handle_sparse = false ;
922+ }
883923
884- let has_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
885- let mut written = 0u64 ;
886- while written < len {
887- let copy_result = if has_copy_file_range {
888- let bytes_to_copy = cmp:: min ( len - written, usize:: max_value ( ) as u64 ) as usize ;
889- let copy_result = unsafe {
890- // We actually don't have to adjust the offsets,
891- // because copy_file_range adjusts the file offset automatically
892- cvt ( copy_file_range ( reader. as_raw_fd ( ) ,
893- ptr:: null_mut ( ) ,
894- writer. as_raw_fd ( ) ,
895- ptr:: null_mut ( ) ,
896- bytes_to_copy,
897- 0 )
898- )
899- } ;
900- if let Err ( ref copy_err) = copy_result {
901- match copy_err. raw_os_error ( ) {
924+ let mut use_copy_file_range = HAS_COPY_FILE_RANGE . load ( Ordering :: Relaxed ) ;
925+ let mut use_sendfile = HAS_SENDFILE . load ( Ordering :: Relaxed ) ;
926+
927+ let mut srcpos: i64 = 0 ;
928+
929+ let mut next_beg: libc:: loff_t = if can_handle_sparse {
930+ let ret = unsafe { lseek64 ( fd_in, srcpos, libc:: SEEK_DATA ) } ;
931+ if ret == -1 {
932+ can_handle_sparse = false ;
933+ 0
934+ } else {
935+ ret
936+ }
937+ } else {
938+ 0
939+ } ;
940+
941+ let mut next_end: libc:: loff_t = if can_handle_sparse {
942+ let ret = unsafe { lseek64 ( fd_in, next_beg, libc:: SEEK_HOLE ) } ;
943+ if ret == -1 {
944+ can_handle_sparse = false ;
945+ bytes_to_copy
946+ } else {
947+ ret
948+ }
949+ } else {
950+ bytes_to_copy
951+ } ;
952+
953+ let mut next_len = next_end - next_beg;
954+
955+ while srcpos < bytes_to_copy {
956+ if srcpos != 0 {
957+ if can_handle_sparse {
958+ next_beg = cvt ( unsafe { lseek64 ( fd_in, srcpos, libc:: SEEK_DATA ) } ) ?;
959+ next_end = cvt ( unsafe { lseek64 ( fd_in, next_beg, libc:: SEEK_HOLE ) } ) ?;
960+
961+ next_len = next_end - next_beg;
962+ } else {
963+ next_beg = srcpos;
964+ next_end = bytes_to_copy - srcpos;
965+ }
966+ }
967+
968+ if next_len <= 0 {
969+ srcpos = next_end;
970+ continue ;
971+ }
972+
973+ let num = if use_copy_file_range {
974+ match cvt ( unsafe {
975+ copy_file_range (
976+ fd_in,
977+ & mut next_beg,
978+ fd_out,
979+ & mut next_beg,
980+ next_len as usize ,
981+ 0 ,
982+ )
983+ } ) {
984+ Ok ( n) => n as isize ,
985+ Err ( err) => match err. raw_os_error ( ) {
986+ // Try fallback if either:
987+ // - Kernel version is < 4.5 (ENOSYS)
988+ // - Files are mounted on different fs (EXDEV)
989+ // - copy_file_range is disallowed, for example by seccomp (EPERM)
902990 Some ( libc:: ENOSYS ) | Some ( libc:: EPERM ) => {
903991 HAS_COPY_FILE_RANGE . store ( false , Ordering :: Relaxed ) ;
992+ use_copy_file_range = false ;
993+ continue ;
904994 }
905- _ => { }
906- }
995+ Some ( libc:: EXDEV ) | Some ( libc:: EINVAL ) => {
996+ use_copy_file_range = false ;
997+ continue ;
998+ }
999+ _ => {
1000+ return Err ( err) ;
1001+ }
1002+ } ,
1003+ }
1004+ } else if use_sendfile {
1005+ if can_handle_sparse && next_beg != 0 {
1006+ cvt ( unsafe { lseek64 ( fd_out, next_beg, libc:: SEEK_SET ) } ) ?;
1007+ }
1008+ match cvt ( unsafe { libc:: sendfile ( fd_out, fd_in, & mut next_beg, next_len as usize ) } ) {
1009+ Ok ( n) => n,
1010+ Err ( err) => match err. raw_os_error ( ) {
1011+ // Try fallback if either:
1012+ // - Kernel version is < 2.2 (ENOSYS)
1013+ // - sendfile is disallowed, for example by seccomp (EPERM)
1014+ // - can't use sendfile on source or destination (EINVAL)
1015+ Some ( libc:: ENOSYS ) | Some ( libc:: EPERM ) => {
1016+ HAS_SENDFILE . store ( false , Ordering :: Relaxed ) ;
1017+ use_sendfile = false ;
1018+ continue ;
1019+ }
1020+ Some ( libc:: EINVAL ) => {
1021+ use_sendfile = false ;
1022+ continue ;
1023+ }
1024+ _ => {
1025+ return Err ( err) ;
1026+ }
1027+ } ,
9071028 }
908- copy_result
9091029 } else {
910- Err ( io:: Error :: from_raw_os_error ( libc:: ENOSYS ) )
911- } ;
912- match copy_result {
913- Ok ( ret) => written += ret as u64 ,
914- Err ( err) => {
915- match err. raw_os_error ( ) {
916- Some ( os_err) if os_err == libc:: ENOSYS
917- || os_err == libc:: EXDEV
918- || os_err == libc:: EPERM => {
919- // Try fallback io::copy if either:
920- // - Kernel version is < 4.5 (ENOSYS)
921- // - Files are mounted on different fs (EXDEV)
922- // - copy_file_range is disallowed, for example by seccomp (EPERM)
923- assert_eq ! ( written, 0 ) ;
924- let ret = io:: copy ( & mut reader, & mut writer) ?;
925- writer. set_permissions ( perm) ?;
926- return Ok ( ret)
927- } ,
928- _ => return Err ( err) ,
1030+ if can_handle_sparse {
1031+ cvt ( unsafe { lseek64 ( fd_in, next_beg, libc:: SEEK_SET ) } ) ?;
1032+ if next_beg != 0 {
1033+ cvt ( unsafe { lseek64 ( fd_out, next_beg, libc:: SEEK_SET ) } ) ?;
9291034 }
9301035 }
931- }
1036+ const DEFAULT_BUF_SIZE : usize = :: sys_common:: io:: DEFAULT_BUF_SIZE ;
1037+ let mut buf = unsafe {
1038+ let buf: [ u8 ; DEFAULT_BUF_SIZE ] = mem:: uninitialized ( ) ;
1039+ buf
1040+ } ;
1041+
1042+ let mut written = 0 ;
1043+ while next_len > 0 {
1044+ let slice_len = cmp:: min ( next_len as usize , DEFAULT_BUF_SIZE ) ;
1045+ let len = match reader. read ( & mut buf[ ..slice_len] ) {
1046+ Ok ( 0 ) => {
1047+ // break early out of copy loop, because nothing is to be read anymore
1048+ srcpos += written;
1049+ break ;
1050+ }
1051+ Ok ( len) => len,
1052+ Err ( ref err) if err. kind ( ) == io:: ErrorKind :: Interrupted => continue ,
1053+ Err ( err) => return Err ( err) ,
1054+ } ;
1055+ writer. write_all ( & buf[ ..len] ) ?;
1056+ written += len as i64 ;
1057+ next_len -= len as i64 ;
1058+ }
1059+ written as isize
1060+ } ;
1061+ srcpos += num as i64 ;
9321062 }
933- writer . set_permissions ( perm ) ? ;
934- Ok ( written )
1063+
1064+ Ok ( srcpos as u64 )
9351065}
0 commit comments