66
77use libc:: { SEEK_DATA , SEEK_HOLE } ;
88use std:: fs:: { File , OpenOptions } ;
9- use std:: io:: Read ;
9+ use std:: io:: { self , Read } ;
1010use std:: os:: unix:: fs:: FileExt ;
1111use std:: os:: unix:: fs:: MetadataExt ;
1212use std:: os:: unix:: fs:: { FileTypeExt , OpenOptionsExt } ;
1313use std:: os:: unix:: io:: AsRawFd ;
1414use std:: path:: Path ;
1515use uucore:: buf_copy;
16- use uucore:: mode :: get_umask ;
16+ use uucore:: fs :: copy_file_with_secure_permissions ;
1717use uucore:: translate;
1818
1919use crate :: {
2020 CopyDebug , CopyResult , CpError , OffloadReflinkDebug , ReflinkMode , SparseDebug , SparseMode ,
21- is_stream,
2221} ;
2322
2423/// The fallback behavior for [`clone`] on failed system call.
@@ -54,21 +53,41 @@ enum CopyMethod {
5453///
5554/// `fallback` controls what to do if the system call fails.
5655#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
57- fn clone < P > ( source : P , dest : P , fallback : CloneFallback ) -> std :: io:: Result < ( ) >
56+ fn clone < P > ( source : P , dest : P , fallback : CloneFallback ) -> io:: Result < ( ) >
5857where
5958 P : AsRef < Path > ,
6059{
6160 let src_file = File :: open ( & source) ?;
62- let dst_file = File :: create ( & dest) ?;
61+ // Create destination with restrictive permissions initially (to prevent race conditions)
62+ // Mode 0o600 means read/write for owner only
63+ let dst_file = OpenOptions :: new ( )
64+ . create ( true )
65+ . write ( true )
66+ . truncate ( false )
67+ . mode ( 0o600 )
68+ . open ( & dest) ?;
6369 let src_fd = src_file. as_raw_fd ( ) ;
6470 let dst_fd = dst_file. as_raw_fd ( ) ;
6571 let result = unsafe { libc:: ioctl ( dst_fd, libc:: FICLONE , src_fd) } ;
6672 if result == 0 {
6773 return Ok ( ( ) ) ;
6874 }
75+ let err = io:: Error :: last_os_error ( ) ;
6976 match fallback {
70- CloneFallback :: Error => Err ( std:: io:: Error :: last_os_error ( ) ) ,
71- CloneFallback :: FSCopy => std:: fs:: copy ( source, dest) . map ( |_| ( ) ) ,
77+ CloneFallback :: Error => {
78+ // FICLONE fails with EINVAL if the destination is not empty.
79+ // Truncate and retry once before giving up.
80+ if err. raw_os_error ( ) == Some ( libc:: EINVAL ) {
81+ dst_file. set_len ( 0 ) ?;
82+ let retry = unsafe { libc:: ioctl ( dst_fd, libc:: FICLONE , src_fd) } ;
83+ if retry == 0 {
84+ return Ok ( ( ) ) ;
85+ }
86+ return Err ( io:: Error :: last_os_error ( ) ) ;
87+ }
88+ Err ( err)
89+ }
90+ CloneFallback :: FSCopy => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
7291 CloneFallback :: SparseCopy => sparse_copy ( source, dest) ,
7392 CloneFallback :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
7493 }
7897/// This function returns a tuple of (bool, u64, u64) signifying a tuple of (whether a file has
7998/// data, its size, no of blocks it has allocated in disk)
8099#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
81- fn check_for_data ( source : & Path ) -> Result < ( bool , u64 , u64 ) , std :: io:: Error > {
100+ fn check_for_data ( source : & Path ) -> Result < ( bool , u64 , u64 ) , io:: Error > {
82101 let mut src_file = File :: open ( source) ?;
83102 let metadata = src_file. metadata ( ) ?;
84103
@@ -98,14 +117,14 @@ fn check_for_data(source: &Path) -> Result<(bool, u64, u64), std::io::Error> {
98117 match result {
99118 -1 => Ok ( ( false , size, blocks) ) , // No data found or end of file
100119 _ if result >= 0 => Ok ( ( true , size, blocks) ) , // Data found
101- _ => Err ( std :: io:: Error :: last_os_error ( ) ) ,
120+ _ => Err ( io:: Error :: last_os_error ( ) ) ,
102121 }
103122}
104123
105124#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
106125/// Checks whether a file is sparse i.e. it contains holes, uses the crude heuristic blocks < size / 512
107126/// Reference:`<https://doc.rust-lang.org/std/os/unix/fs/trait.MetadataExt.html#tymethod.blocks>`
108- fn check_sparse_detection ( source : & Path ) -> Result < bool , std :: io:: Error > {
127+ fn check_sparse_detection ( source : & Path ) -> Result < bool , io:: Error > {
109128 let src_file = File :: open ( source) ?;
110129 let metadata = src_file. metadata ( ) ?;
111130 let size = metadata. size ( ) ;
@@ -120,17 +139,24 @@ fn check_sparse_detection(source: &Path) -> Result<bool, std::io::Error> {
120139/// Optimized [`sparse_copy`] doesn't create holes for large sequences of zeros in non `sparse_files`
121140/// Used when `--sparse=auto`
122141#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
123- fn sparse_copy_without_hole < P > ( source : P , dest : P ) -> std :: io:: Result < ( ) >
142+ fn sparse_copy_without_hole < P > ( source : P , dest : P ) -> io:: Result < ( ) >
124143where
125144 P : AsRef < Path > ,
126145{
127146 let src_file = File :: open ( source) ?;
128- let dst_file = File :: create ( dest) ?;
147+ // Create destination with restrictive permissions initially (to prevent race conditions)
148+ // Mode 0o600 means read/write for owner only
149+ let dst_file = OpenOptions :: new ( )
150+ . create ( true )
151+ . write ( true )
152+ . truncate ( true )
153+ . mode ( 0o600 )
154+ . open ( dest) ?;
129155 let dst_fd = dst_file. as_raw_fd ( ) ;
130156
131157 let size = src_file. metadata ( ) ?. size ( ) ;
132158 if unsafe { libc:: ftruncate ( dst_fd, size. try_into ( ) . unwrap ( ) ) } < 0 {
133- return Err ( std :: io:: Error :: last_os_error ( ) ) ;
159+ return Err ( io:: Error :: last_os_error ( ) ) ;
134160 }
135161 let src_fd = src_file. as_raw_fd ( ) ;
136162 let mut current_offset: isize = 0 ;
@@ -152,7 +178,7 @@ where
152178 break ;
153179 }
154180 if result <= -2 || hole <= -2 {
155- return Err ( std :: io:: Error :: last_os_error ( ) ) ;
181+ return Err ( io:: Error :: last_os_error ( ) ) ;
156182 }
157183 let len: isize = hole - current_offset;
158184 // Read and write data in chunks of `step` while reusing the same buffer
@@ -170,17 +196,24 @@ where
170196/// Perform a sparse copy from one file to another.
171197/// Creates a holes for large sequences of zeros in `non_sparse_files`, used for `--sparse=always`
172198#[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
173- fn sparse_copy < P > ( source : P , dest : P ) -> std :: io:: Result < ( ) >
199+ fn sparse_copy < P > ( source : P , dest : P ) -> io:: Result < ( ) >
174200where
175201 P : AsRef < Path > ,
176202{
177203 let mut src_file = File :: open ( source) ?;
178- let dst_file = File :: create ( dest) ?;
204+ // Create destination with restrictive permissions initially (to prevent race conditions)
205+ // Mode 0o600 means read/write for owner only
206+ let dst_file = OpenOptions :: new ( )
207+ . create ( true )
208+ . write ( true )
209+ . truncate ( true )
210+ . mode ( 0o600 )
211+ . open ( dest) ?;
179212 let dst_fd = dst_file. as_raw_fd ( ) ;
180213
181214 let size: usize = src_file. metadata ( ) ?. size ( ) . try_into ( ) . unwrap ( ) ;
182215 if unsafe { libc:: ftruncate ( dst_fd, size. try_into ( ) . unwrap ( ) ) } < 0 {
183- return Err ( std :: io:: Error :: last_os_error ( ) ) ;
216+ return Err ( io:: Error :: last_os_error ( ) ) ;
184217 }
185218
186219 let blksize = dst_file. metadata ( ) ?. blksize ( ) ;
@@ -214,7 +247,7 @@ fn check_dest_is_fifo(dest: &Path) -> bool {
214247}
215248
216249/// Copy the contents of a stream from `source` to `dest`.
217- fn copy_stream < P > ( source : P , dest : P ) -> std :: io:: Result < u64 >
250+ fn copy_stream < P > ( source : P , dest : P ) -> io:: Result < u64 >
218251where
219252 P : AsRef < Path > ,
220253{
@@ -237,21 +270,18 @@ where
237270 // TODO Update the code below to respect the case where
238271 // `--preserve=ownership` is not true.
239272 let mut src_file = File :: open ( & source) ?;
240- let mode = 0o622 & !get_umask ( ) ;
273+ // Create with restrictive permissions initially to prevent race conditions
274+ // Mode 0o600 means read/write for owner only
275+ // Use truncate(true) to ensure we overwrite existing content
241276 let mut dst_file = OpenOptions :: new ( )
242277 . create ( true )
243278 . write ( true )
244- . mode ( mode)
279+ . truncate ( true )
280+ . mode ( 0o600 )
245281 . open ( & dest) ?;
246282
247- let dest_is_stream = is_stream ( & dst_file. metadata ( ) ?) ;
248- if !dest_is_stream {
249- // `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually.
250- dst_file. set_len ( 0 ) ?;
251- }
252-
253283 let num_bytes_copied = buf_copy:: copy_stream ( & mut src_file, & mut dst_file)
254- . map_err ( |e| std :: io:: Error :: other ( format ! ( "{e}" ) ) ) ?;
284+ . map_err ( |e| io:: Error :: other ( format ! ( "{e}" ) ) ) ?;
255285
256286 Ok ( num_bytes_copied)
257287}
@@ -287,7 +317,9 @@ pub(crate) fn copy_on_write(
287317 }
288318
289319 match copy_method {
290- CopyMethod :: FSCopy => std:: fs:: copy ( source, dest) . map ( |_| ( ) ) ,
320+ CopyMethod :: FSCopy => {
321+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
322+ }
291323 _ => sparse_copy ( source, dest) ,
292324 }
293325 }
@@ -303,7 +335,7 @@ pub(crate) fn copy_on_write(
303335 if let Ok ( debug) = result {
304336 copy_debug = debug;
305337 }
306- std :: fs :: copy ( source, dest) . map ( |_| ( ) )
338+ copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) )
307339 }
308340 }
309341 ( ReflinkMode :: Never , SparseMode :: Auto ) => {
@@ -322,7 +354,7 @@ pub(crate) fn copy_on_write(
322354
323355 match copy_method {
324356 CopyMethod :: SparseCopyWithoutHole => sparse_copy_without_hole ( source, dest) ,
325- _ => std :: fs :: copy ( source, dest) . map ( |_| ( ) ) ,
357+ _ => copy_file_with_secure_permissions ( source, dest) . map ( |_| ( ) ) ,
326358 }
327359 }
328360 }
@@ -401,7 +433,7 @@ pub(crate) fn copy_on_write(
401433fn handle_reflink_auto_sparse_always (
402434 source : & Path ,
403435 dest : & Path ,
404- ) -> Result < ( CopyDebug , CopyMethod ) , std :: io:: Error > {
436+ ) -> Result < ( CopyDebug , CopyMethod ) , io:: Error > {
405437 let mut copy_debug = CopyDebug {
406438 offload : OffloadReflinkDebug :: Unknown ,
407439 reflink : OffloadReflinkDebug :: Unsupported ,
@@ -438,7 +470,7 @@ fn handle_reflink_auto_sparse_always(
438470
439471/// Handles debug results when flags are "--reflink=auto" and "--sparse=auto" and specifies what
440472/// type of copy should be used
441- fn handle_reflink_never_sparse_never ( source : & Path ) -> Result < CopyDebug , std :: io:: Error > {
473+ fn handle_reflink_never_sparse_never ( source : & Path ) -> Result < CopyDebug , io:: Error > {
442474 let mut copy_debug = CopyDebug {
443475 offload : OffloadReflinkDebug :: Unknown ,
444476 reflink : OffloadReflinkDebug :: No ,
@@ -459,7 +491,7 @@ fn handle_reflink_never_sparse_never(source: &Path) -> Result<CopyDebug, std::io
459491
460492/// Handles debug results when flags are "--reflink=auto" and "--sparse=never", files will be copied
461493/// through cloning them with fallback switching to [`std::fs::copy`]
462- fn handle_reflink_auto_sparse_never ( source : & Path ) -> Result < CopyDebug , std :: io:: Error > {
494+ fn handle_reflink_auto_sparse_never ( source : & Path ) -> Result < CopyDebug , io:: Error > {
463495 let mut copy_debug = CopyDebug {
464496 offload : OffloadReflinkDebug :: Unknown ,
465497 reflink : OffloadReflinkDebug :: No ,
@@ -484,7 +516,7 @@ fn handle_reflink_auto_sparse_never(source: &Path) -> Result<CopyDebug, std::io:
484516fn handle_reflink_auto_sparse_auto (
485517 source : & Path ,
486518 dest : & Path ,
487- ) -> Result < ( CopyDebug , CopyMethod ) , std :: io:: Error > {
519+ ) -> Result < ( CopyDebug , CopyMethod ) , io:: Error > {
488520 let mut copy_debug = CopyDebug {
489521 offload : OffloadReflinkDebug :: Unknown ,
490522 reflink : OffloadReflinkDebug :: Unsupported ,
@@ -527,7 +559,7 @@ fn handle_reflink_auto_sparse_auto(
527559fn handle_reflink_never_sparse_auto (
528560 source : & Path ,
529561 dest : & Path ,
530- ) -> Result < ( CopyDebug , CopyMethod ) , std :: io:: Error > {
562+ ) -> Result < ( CopyDebug , CopyMethod ) , io:: Error > {
531563 let mut copy_debug = CopyDebug {
532564 offload : OffloadReflinkDebug :: Unknown ,
533565 reflink : OffloadReflinkDebug :: No ,
@@ -563,7 +595,7 @@ fn handle_reflink_never_sparse_auto(
563595fn handle_reflink_never_sparse_always (
564596 source : & Path ,
565597 dest : & Path ,
566- ) -> Result < ( CopyDebug , CopyMethod ) , std :: io:: Error > {
598+ ) -> Result < ( CopyDebug , CopyMethod ) , io:: Error > {
567599 let mut copy_debug = CopyDebug {
568600 offload : OffloadReflinkDebug :: Unknown ,
569601 reflink : OffloadReflinkDebug :: No ,
0 commit comments