66//! Recursively copy the contents of a directory.
77//!
88//! See the [`copy_directory`] function for more information.
9- #[ cfg( windows) ]
109use std:: borrow:: Cow ;
1110use std:: collections:: { HashMap , HashSet } ;
1211use std:: convert:: identity;
@@ -24,7 +23,7 @@ use uucore::fs::{
2423use uucore:: show;
2524use uucore:: translate;
2625use uucore:: uio_error;
27- use walkdir:: { DirEntry , WalkDir } ;
26+ use walkdir:: WalkDir ;
2827
2928#[ cfg( all( feature = "selinux" , target_os = "linux" ) ) ]
3029use crate :: set_selinux_context;
@@ -44,7 +43,7 @@ struct DirNeedingPermissions {
4443}
4544
4645/// Ensure a Windows path starts with a `\\?`.
47- #[ cfg( target_os = " windows" ) ]
46+ #[ cfg( windows) ]
4847fn adjust_canonicalization ( p : & Path ) -> Cow < ' _ , Path > {
4948 // In some cases, \\? can be missing on some Windows paths. Add it at the
5049 // beginning unless the path is prefixed with a device namespace.
@@ -66,39 +65,6 @@ fn adjust_canonicalization(p: &Path) -> Cow<'_, Path> {
6665 }
6766}
6867
69- /// Get a descendant path relative to the given parent directory.
70- ///
71- /// If `root_parent` is `None`, then this just returns the `path`
72- /// itself. Otherwise, this function strips the parent prefix from the
73- /// given `path`, leaving only the portion of the path relative to the
74- /// parent.
75- fn get_local_to_root_parent (
76- path : & Path ,
77- root_parent : Option < & Path > ,
78- ) -> Result < PathBuf , StripPrefixError > {
79- match root_parent {
80- Some ( parent) => {
81- // On Windows, some paths are starting with \\?
82- // but not always, so, make sure that we are consistent for strip_prefix
83- // See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
84- #[ cfg( windows) ]
85- let ( path, parent) = (
86- adjust_canonicalization ( path) ,
87- adjust_canonicalization ( parent) ,
88- ) ;
89- let path = path. strip_prefix ( parent) ?;
90- Ok ( path. to_path_buf ( ) )
91- }
92- None => Ok ( path. to_path_buf ( ) ) ,
93- }
94- }
95-
96- /// Given an iterator, return all its items except the last.
97- fn skip_last < T > ( mut iter : impl Iterator < Item = T > ) -> impl Iterator < Item = T > {
98- let last = iter. next ( ) ;
99- iter. scan ( last, Option :: replace)
100- }
101-
10268/// Paths that are invariant throughout the traversal when copying a directory.
10369struct Context < ' a > {
10470 /// The current working directory at the time of starting the traversal.
@@ -120,11 +86,11 @@ struct Context<'a> {
12086impl < ' a > Context < ' a > {
12187 fn new ( root : & ' a Path , target : & ' a Path ) -> io:: Result < Self > {
12288 let current_dir = env:: current_dir ( ) ?;
123- let root_path = current_dir. join ( root) ;
89+ let mut root_path = current_dir. join ( root) ;
12490 let target_is_file = target. is_file ( ) ;
12591 let root_parent =
12692 if target. exists ( ) && !root. as_os_str ( ) . as_encoded_bytes ( ) . ends_with ( b"/." ) {
127- root_path. parent ( ) . map ( ToOwned :: to_owned )
93+ root_path. pop ( ) . then_some ( root_path )
12894 } else if root == Path :: new ( "." ) && target. is_dir ( ) {
12995 // Special case: when copying current directory (.) to an existing directory,
13096 // we don't want to use the parent path as root_parent because we want to
@@ -179,12 +145,12 @@ impl<'a> Context<'a> {
179145/// }
180146/// ];
181147/// ```
182- struct Entry {
148+ struct Entry < ' a > {
183149 /// The absolute path to file or directory to copy.
184150 source_absolute : PathBuf ,
185151
186152 /// The relative path to file or directory to copy.
187- source_relative : PathBuf ,
153+ source_relative : & ' a Path ,
188154
189155 /// The path to the destination, relative to the target.
190156 local_to_target : PathBuf ,
@@ -193,24 +159,44 @@ struct Entry {
193159 target_is_file : bool ,
194160}
195161
196- impl Entry {
197- fn new < A : AsRef < Path > > (
162+ impl < ' a > Entry < ' a > {
163+ fn new (
198164 context : & Context ,
199- source : A ,
165+ source : & ' a Path ,
200166 no_target_dir : bool ,
201167 ) -> Result < Self , StripPrefixError > {
202- let source = source. as_ref ( ) ;
203- let source_relative = source. to_path_buf ( ) ;
204- let source_absolute = context. current_dir . join ( & source_relative) ;
205- let mut descendant =
206- get_local_to_root_parent ( & source_absolute, context. root_parent . as_deref ( ) ) ?;
168+ let source_relative = source;
169+ let source_absolute = context. current_dir . join ( source_relative) ;
170+
171+ // Get a descendant path relative to the given parent directory.
172+ // On Windows, some paths are starting with \\?
173+ // but not always, so, make sure that we are consistent for strip_prefix
174+ // See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
175+ #[ cfg( windows) ]
176+ let ( path, parent) = (
177+ adjust_canonicalization ( & source_absolute) ,
178+ context
179+ . root_parent
180+ . as_deref ( )
181+ . map ( |p| adjust_canonicalization ( p) ) ,
182+ ) ;
183+ #[ cfg( not( windows) ) ]
184+ let ( path, parent) = (
185+ Cow :: from ( & source_absolute) ,
186+ context. root_parent . as_deref ( ) . map ( Cow :: from) ,
187+ ) ;
188+ let mut descendant = match parent {
189+ Some ( parent) => path. strip_prefix ( parent) ?,
190+ None => & path,
191+ } ;
192+
207193 if no_target_dir {
208194 let source_is_dir = source. is_dir ( ) ;
209195 if path_ends_with_terminator ( context. target )
210196 && source_is_dir
211197 && !exists ( context. target ) . is_ok_and ( identity)
212198 {
213- if let Err ( e) = fs:: create_dir_all ( context. target ) {
199+ if let Err ( e) = fs:: create_dir ( context. target ) {
214200 eprintln ! (
215201 "{}" ,
216202 translate!( "cp-error-failed-to-create-directory" , "error" => e)
@@ -222,7 +208,7 @@ impl Entry {
222208 . next_back ( )
223209 . and_then ( |stripped| descendant. strip_prefix ( stripped) . ok ( ) )
224210 {
225- descendant = stripped. to_path_buf ( ) ;
211+ descendant = stripped;
226212 }
227213 } else if context. root == Path :: new ( "." ) && context. target . is_dir ( ) {
228214 // Special case: when copying current directory (.) to an existing directory,
@@ -233,7 +219,7 @@ impl Entry {
233219 if let Some ( current_dir_name) = context. current_dir . file_name ( )
234220 && let Ok ( stripped) = descendant. strip_prefix ( current_dir_name)
235221 {
236- descendant = stripped. to_path_buf ( ) ;
222+ descendant = stripped;
237223 }
238224 }
239225
@@ -288,7 +274,7 @@ fn copy_direntry(
288274 if options. verbose {
289275 println ! (
290276 "{}" ,
291- context_for( & entry. source_relative, & entry. local_to_target)
277+ context_for( entry. source_relative, & entry. local_to_target)
292278 ) ;
293279 }
294280 Ok ( true )
@@ -299,7 +285,7 @@ fn copy_direntry(
299285 if !source_is_dir
300286 && let Err ( err) = copy_file (
301287 progress_bar,
302- & entry. source_relative ,
288+ entry. source_relative ,
303289 entry. local_to_target . as_path ( ) ,
304290 options,
305291 symlinked_files,
@@ -400,7 +386,7 @@ pub(crate) fn copy_directory(
400386 // a -> d/a
401387 // a/b -> d/a/b
402388 //
403- let tmp = if options. parents {
389+ let target = if options. parents {
404390 if let Some ( parent) = root. parent ( ) {
405391 let new_target = target. join ( parent) ;
406392 build_dir ( & new_target, true ) ?;
@@ -415,31 +401,26 @@ pub(crate) fn copy_directory(
415401 println ! ( "{} -> {}" , x. display( ) , y. display( ) ) ;
416402 }
417403 }
418-
419- new_target
404+ Cow :: from ( new_target)
420405 } else {
421- target . to_path_buf ( )
406+ Cow :: from ( target )
422407 }
423408 } else {
424- target . to_path_buf ( )
409+ Cow :: from ( target )
425410 } ;
426- let target = tmp. as_path ( ) ;
427411
428412 let preserve_hard_links = options. preserve_hard_links ( ) ;
429413
430414 // Collect some paths here that are invariant during the traversal
431415 // of the given directory, like the current working directory and
432416 // the target directory.
433- let context = match Context :: new ( root, target) {
417+ let context = match Context :: new ( root, & target) {
434418 Ok ( c) => c,
435419 Err ( e) => {
436420 return Err ( translate ! ( "cp-error-failed-get-current-dir" , "error" => e) . into ( ) ) ;
437421 }
438422 } ;
439423
440- // The directory we were in during the previous iteration
441- let mut last_iter: Option < DirEntry > = None ;
442-
443424 // Keep track of all directories we've created that need permission fixes
444425 let mut dirs_needing_permissions: Vec < DirNeedingPermissions > = Vec :: new ( ) ;
445426
@@ -481,13 +462,6 @@ pub(crate) fn copy_directory(
481462 // to prevent other users from accessing them before they're done.
482463 // We thus need to fix the permissions of each directory we copy
483464 // once it's contents are ready.
484- // This "fixup" is implemented here in a memory-efficient manner.
485- //
486- // We detect iterations where we "walk up" the directory tree,
487- // and fix permissions on all the directories we exited.
488- // (Note that there can be more than one! We might step out of
489- // `./a/b/c` into `./a/`, in which case we'll need to fix the
490- // permissions of both `./a/b/c` and `./a/b`, in that order.)
491465 let is_dir_for_permissions =
492466 entry_is_dir_no_follow || ( options. dereference && direntry_path. is_dir ( ) ) ;
493467 if is_dir_for_permissions {
@@ -506,51 +480,10 @@ pub(crate) fn copy_directory(
506480 }
507481 // Add this directory to our list for permission fixing later
508482 dirs_needing_permissions. push ( DirNeedingPermissions {
509- source : entry. source_absolute . clone ( ) ,
510- dest : entry. local_to_target . clone ( ) ,
483+ source : entry. source_absolute ,
484+ dest : entry. local_to_target ,
511485 was_created : created,
512486 } ) ;
513-
514- // If true, last_iter is not a parent of this iter.
515- // The means we just exited a directory.
516- let went_up = if let Some ( last_iter) = & last_iter {
517- last_iter. path ( ) . strip_prefix ( direntry_path) . is_ok ( )
518- } else {
519- false
520- } ;
521-
522- if went_up {
523- // Compute the "difference" between `last_iter` and `direntry`.
524- // For example, if...
525- // - last_iter = `a/b/c/d`
526- // - direntry = `a/b`
527- // then diff = `c/d`
528- //
529- // All the unwraps() here are unreachable.
530- let last_iter = last_iter. as_ref ( ) . unwrap ( ) ;
531- let diff = last_iter. path ( ) . strip_prefix ( direntry_path) . unwrap ( ) ;
532-
533- // Fix permissions for every entry in `diff`, inside-out.
534- // We skip the last directory (which will be `.`) because
535- // its permissions will be fixed when we walk _out_ of it.
536- // (at this point, we might not be done copying `.`!)
537- for p in skip_last ( diff. ancestors ( ) ) {
538- let src = direntry_path. join ( p) ;
539- let entry = Entry :: new ( & context, & src, options. no_target_dir ) ?;
540-
541- copy_attributes (
542- & entry. source_absolute ,
543- & entry. local_to_target ,
544- & options. attributes ,
545- false ,
546- options. set_selinux_context ,
547- #[ cfg( unix) ]
548- orig_umask,
549- ) ?;
550- }
551- }
552-
553- last_iter = Some ( direntry) ;
554487 }
555488 }
556489
@@ -588,7 +521,7 @@ pub(crate) fn copy_directory(
588521 & src,
589522 y,
590523 & options. attributes ,
591- false ,
524+ true ,
592525 options. set_selinux_context ,
593526 #[ cfg( unix) ]
594527 orig_umask,
0 commit comments