@@ -23,7 +23,7 @@ use uucore::fs::{
2323use uucore:: show;
2424use uucore:: show_error;
2525use uucore:: uio_error;
26- use walkdir:: WalkDir ;
26+ use walkdir:: { DirEntry , WalkDir } ;
2727
2828use crate :: {
2929 aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, CopyResult , Error ,
@@ -79,6 +79,12 @@ fn get_local_to_root_parent(
7979 }
8080}
8181
82+ /// Given an iterator, return all its items except the last.
83+ fn skip_last < T > ( mut iter : impl Iterator < Item = T > ) -> impl Iterator < Item = T > {
84+ let last = iter. next ( ) ;
85+ iter. scan ( last, |state, item| std:: mem:: replace ( state, Some ( item) ) )
86+ }
87+
8288/// Paths that are invariant throughout the traversal when copying a directory.
8389struct Context < ' a > {
8490 /// The current working directory at the time of starting the traversal.
@@ -248,7 +254,7 @@ fn copy_direntry(
248254 if target_is_file {
249255 return Err ( "cannot overwrite non-directory with directory" . into ( ) ) ;
250256 } else {
251- build_dir ( & local_to_target, false , options) ?;
257+ build_dir ( & local_to_target, false , options, Some ( & source_absolute ) ) ?;
252258 if options. verbose {
253259 println ! ( "{}" , context_for( & source_relative, & local_to_target) ) ;
254260 }
@@ -373,7 +379,7 @@ pub(crate) fn copy_directory(
373379 let tmp = if options. parents {
374380 if let Some ( parent) = root. parent ( ) {
375381 let new_target = target. join ( parent) ;
376- build_dir ( & new_target, true , options) ?;
382+ build_dir ( & new_target, true , options, None ) ?;
377383 if options. verbose {
378384 // For example, if copying file `a/b/c` and its parents
379385 // to directory `d/`, then print
@@ -405,6 +411,9 @@ pub(crate) fn copy_directory(
405411 Err ( e) => return Err ( format ! ( "failed to get current directory {e}" ) . into ( ) ) ,
406412 } ;
407413
414+ // The directory we were in during the previous iteration
415+ let mut last_iter: Option < DirEntry > = None ;
416+
408417 // Traverse the contents of the directory, copying each one.
409418 for direntry_result in WalkDir :: new ( root)
410419 . same_file_system ( options. one_file_system )
@@ -423,23 +432,93 @@ pub(crate) fn copy_directory(
423432 copied_destinations,
424433 copied_files,
425434 ) ?;
435+
436+ // We omit certain permissions when creating directories
437+ // to prevent other users from accessing them before they're done.
438+ // We thus need to fix the permissions of each directory we copy
439+ // once it's contents are ready.
440+ // This "fixup" is implemented here in a memory-efficient manner.
441+ //
442+ // We detect iterations where we "walk up" the directory tree,
443+ // and fix permissions on all the directories we exited.
444+ // (Note that there can be more than one! We might step out of
445+ // `./a/b/c` into `./a/`, in which case we'll need to fix the
446+ // permissions of both `./a/b/c` and `./a/b`, in that order.)
447+ if direntry. file_type ( ) . is_dir ( ) {
448+ // If true, last_iter is not a parent of this iter.
449+ // The means we just exited a directory.
450+ let went_up = if let Some ( last_iter) = & last_iter {
451+ last_iter. path ( ) . strip_prefix ( direntry. path ( ) ) . is_ok ( )
452+ } else {
453+ false
454+ } ;
455+
456+ if went_up {
457+ // Compute the "difference" between `last_iter` and `direntry`.
458+ // For example, if...
459+ // - last_iter = `a/b/c/d`
460+ // - direntry = `a/b`
461+ // then diff = `c/d`
462+ //
463+ // All the unwraps() here are unreachable.
464+ let last_iter = last_iter. as_ref ( ) . unwrap ( ) ;
465+ let diff = last_iter. path ( ) . strip_prefix ( direntry. path ( ) ) . unwrap ( ) ;
466+
467+ // Fix permissions for every entry in `diff`, inside-out.
468+ // We skip the last directory (which will be `.`) because
469+ // its permissions will be fixed when we walk _out_ of it.
470+ // (at this point, we might not be done copying `.`!)
471+ for p in skip_last ( diff. ancestors ( ) ) {
472+ let src = direntry. path ( ) . join ( p) ;
473+ let entry = Entry :: new ( & context, & src, options. no_target_dir ) ?;
474+
475+ copy_attributes (
476+ & entry. source_absolute ,
477+ & entry. local_to_target ,
478+ & options. attributes ,
479+ ) ?;
480+ }
481+ }
482+
483+ last_iter = Some ( direntry) ;
484+ }
426485 }
486+
427487 // Print an error message, but continue traversing the directory.
428488 Err ( e) => show_error ! ( "{}" , e) ,
429489 }
430490 }
431491
432- // Copy the attributes from the root directory to the target directory.
492+ // Handle final directory permission fixes.
493+ // This is almost the same as the permission-fixing code above,
494+ // with minor differences (commented)
495+ if let Some ( last_iter) = last_iter {
496+ let diff = last_iter. path ( ) . strip_prefix ( root) . unwrap ( ) ;
497+
498+ // Do _not_ skip `.` this time, since we know we're done.
499+ // This is where we fix the permissions of the top-level
500+ // directory we just copied.
501+ for p in diff. ancestors ( ) {
502+ let src = root. join ( p) ;
503+ let entry = Entry :: new ( & context, & src, options. no_target_dir ) ?;
504+
505+ copy_attributes (
506+ & entry. source_absolute ,
507+ & entry. local_to_target ,
508+ & options. attributes ,
509+ ) ?;
510+ }
511+ }
512+
513+ // Also fix permissions for parent directories,
514+ // if we were asked to create them.
433515 if options. parents {
434516 let dest = target. join ( root. file_name ( ) . unwrap ( ) ) ;
435- copy_attributes ( root, dest. as_path ( ) , & options. attributes ) ?;
436517 for ( x, y) in aligned_ancestors ( root, dest. as_path ( ) ) {
437518 if let Ok ( src) = canonicalize ( x, MissingHandling :: Normal , ResolveMode :: Physical ) {
438519 copy_attributes ( & src, y, & options. attributes ) ?;
439520 }
440521 }
441- } else {
442- copy_attributes ( root, target, & options. attributes ) ?;
443522 }
444523
445524 Ok ( ( ) )
@@ -472,13 +551,21 @@ pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result<bool> {
472551/// Builds a directory at the specified path with the given options.
473552///
474553/// # Notes
475- /// - It excludes certain permissions if ownership or special mode bits could
476- /// potentially change.
554+ /// - If `copy_attributes_from` is `Some`, the new directory's attributes will be
555+ /// copied from the provided file. Otherwise, the new directory will have the default
556+ /// attributes for the current user.
557+ /// - This method excludes certain permissions if ownership or special mode bits could
558+ /// potentially change. (See `test_dir_perm_race_with_preserve_mode_and_ownership``)
477559/// - The `recursive` flag determines whether parent directories should be created
478560/// if they do not already exist.
479561// we need to allow unused_variable since `options` might be unused in non unix systems
480562#[ allow( unused_variables) ]
481- fn build_dir ( path : & PathBuf , recursive : bool , options : & Options ) -> CopyResult < ( ) > {
563+ fn build_dir (
564+ path : & PathBuf ,
565+ recursive : bool ,
566+ options : & Options ,
567+ copy_attributes_from : Option < & Path > ,
568+ ) -> CopyResult < ( ) > {
482569 let mut builder = fs:: DirBuilder :: new ( ) ;
483570 builder. recursive ( recursive) ;
484571
@@ -487,6 +574,9 @@ fn build_dir(path: &PathBuf, recursive: bool, options: &Options) -> CopyResult<(
487574 // could potentially change.
488575 #[ cfg( unix) ]
489576 {
577+ use crate :: Preserve ;
578+ use std:: os:: unix:: fs:: PermissionsExt ;
579+
490580 // we need to allow trivial casts here because some systems like linux have u32 constants in
491581 // in libc while others don't.
492582 #[ allow( clippy:: unnecessary_cast) ]
@@ -498,7 +588,18 @@ fn build_dir(path: &PathBuf, recursive: bool, options: &Options) -> CopyResult<(
498588 } else {
499589 0
500590 } as u32 ;
501- excluded_perms |= uucore:: mode:: get_umask ( ) ;
591+
592+ let umask = if copy_attributes_from. is_some ( )
593+ && matches ! ( options. attributes. mode, Preserve :: Yes { .. } )
594+ {
595+ !fs:: symlink_metadata ( copy_attributes_from. unwrap ( ) ) ?
596+ . permissions ( )
597+ . mode ( )
598+ } else {
599+ uucore:: mode:: get_umask ( )
600+ } ;
601+
602+ excluded_perms |= umask;
502603 let mode = !excluded_perms & 0o777 ; //use only the last three octet bits
503604 std:: os:: unix:: fs:: DirBuilderExt :: mode ( & mut builder, mode) ;
504605 }
0 commit comments