@@ -843,12 +843,16 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
843
843
#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
844
844
pub fn copy ( from : & Path , to : & Path ) -> io:: Result < u64 > {
845
845
use cmp;
846
- use fs:: File ;
846
+ use io:: { Read , Write } ;
847
+ use os:: unix:: fs:: { OpenOptionsExt , PermissionsExt } ;
848
+ use fs:: { File , OpenOptions } ;
847
849
use sync:: atomic:: { AtomicBool , Ordering } ;
848
-
849
850
// Kernel prior to 4.5 don't have copy_file_range
850
851
// We store the availability in a global to avoid unnecessary syscalls
851
852
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 ) ;
852
856
853
857
unsafe fn copy_file_range (
854
858
fd_in : libc:: c_int ,
@@ -869,67 +873,193 @@ pub fn copy(from: &Path, to: &Path) -> io::Result<u64> {
869
873
)
870
874
}
871
875
872
- if !from. is_file ( ) {
873
- return Err ( Error :: new ( ErrorKind :: InvalidInput ,
874
- "the source path is not an existing regular file" ) )
875
- }
876
-
877
876
let mut reader = File :: open ( from) ?;
878
- let mut writer = File :: create ( to ) ? ;
879
- let ( perm , len) = {
877
+
878
+ let ( mode , len) = {
880
879
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 ( ) )
882
887
} ;
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
+ }
883
923
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)
902
990
Some ( libc:: ENOSYS ) | Some ( libc:: EPERM ) => {
903
991
HAS_COPY_FILE_RANGE . store ( false , Ordering :: Relaxed ) ;
992
+ use_copy_file_range = false ;
993
+ continue ;
904
994
}
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
+ } ,
907
1028
}
908
- copy_result
909
1029
} 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 ) } ) ?;
929
1034
}
930
1035
}
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 ;
932
1062
}
933
- writer . set_permissions ( perm ) ? ;
934
- Ok ( written )
1063
+
1064
+ Ok ( srcpos as u64 )
935
1065
}
0 commit comments