@@ -31,10 +31,13 @@ use winapi::um::fileapi::CreateFileW;
3131#[ cfg( windows) ]
3232use winapi:: um:: fileapi:: GetFileInformationByHandle ;
3333
34+ use std:: borrow:: Cow ;
35+
3436use clap:: { App , Arg , ArgMatches } ;
3537use filetime:: FileTime ;
3638use quick_error:: ResultExt ;
3739use std:: collections:: HashSet ;
40+ use std:: env;
3841#[ cfg( not( windows) ) ]
3942use std:: ffi:: CString ;
4043#[ cfg( windows) ]
@@ -53,6 +56,7 @@ use std::os::windows::ffi::OsStrExt;
5356use std:: path:: { Path , PathBuf , StripPrefixError } ;
5457use std:: str:: FromStr ;
5558use std:: string:: ToString ;
59+ use uucore:: fs:: resolve_relative_path;
5660use uucore:: fs:: { canonicalize, CanonicalizeMode } ;
5761use walkdir:: WalkDir ;
5862
@@ -795,8 +799,6 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
795799 }
796800 }
797801
798- let dont_follow_symbolic_links = options. no_dereference ;
799-
800802 let mut hard_links: Vec < ( String , u64 ) > = vec ! [ ] ;
801803
802804 let mut non_fatal_errors = false ;
@@ -811,19 +813,7 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
811813 preserve_hardlinks ( & mut hard_links, source, dest, & mut found_hard_link) . unwrap ( ) ;
812814 }
813815
814- if dont_follow_symbolic_links && fs:: symlink_metadata ( & source) ?. file_type ( ) . is_symlink ( )
815- {
816- // Here, we will copy the symlink itself (actually, just recreate it)
817- let link = fs:: read_link ( & source) ?;
818- let dest = if target. is_dir ( ) {
819- // the target is a directory, we need to keep the filename
820- let p = Path :: new ( source. file_name ( ) . unwrap ( ) ) ;
821- target. join ( p)
822- } else {
823- target. clone ( )
824- } ;
825- symlink_file ( & link, & dest, & * context_for ( & link, target) ) ?;
826- } else if !found_hard_link {
816+ if !found_hard_link {
827817 if let Err ( error) = copy_source ( source, target, & target_type, options) {
828818 show_error ! ( "{}" , error) ;
829819 match error {
@@ -882,6 +872,27 @@ fn copy_source(
882872 }
883873}
884874
875+ #[ cfg( target_os = "windows" ) ]
876+ fn adjust_canonicalization < ' a > ( p : & ' a Path ) -> Cow < ' a , Path > {
877+ // In some cases, \\? can be missing on some Windows paths. Add it at the
878+ // beginning unless the path is prefixed with a device namespace.
879+ const VERBATIM_PREFIX : & str = r#"\\?"# ;
880+ const DEVICE_NS_PREFIX : & str = r#"\\."# ;
881+
882+ let has_prefix = p
883+ . components ( )
884+ . next ( )
885+ . and_then ( |comp| comp. as_os_str ( ) . to_str ( ) )
886+ . map ( |p_str| p_str. starts_with ( VERBATIM_PREFIX ) || p_str. starts_with ( DEVICE_NS_PREFIX ) )
887+ . unwrap_or_default ( ) ;
888+
889+ if has_prefix {
890+ p. into ( )
891+ } else {
892+ Path :: new ( VERBATIM_PREFIX ) . join ( p) . into ( )
893+ }
894+ }
895+
885896/// Read the contents of the directory `root` and recursively copy the
886897/// contents to `target`.
887898///
@@ -914,9 +925,35 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
914925 let mut hard_links: Vec < ( String , u64 ) > = vec ! [ ] ;
915926
916927 for path in WalkDir :: new ( root) {
917- let path = or_continue ! ( or_continue!( path) . path( ) . canonicalize( ) ) ;
928+ let p = or_continue ! ( path) ;
929+ let is_symlink = fs:: symlink_metadata ( p. path ( ) ) ?. file_type ( ) . is_symlink ( ) ;
930+ let path = if options. no_dereference && is_symlink {
931+ // we are dealing with a symlink. Don't follow it
932+ match env:: current_dir ( ) {
933+ Ok ( cwd) => cwd. join ( resolve_relative_path ( p. path ( ) ) ) ,
934+ Err ( e) => crash ! ( 1 , "failed to get current directory {}" , e) ,
935+ }
936+ } else {
937+ or_continue ! ( p. path( ) . canonicalize( ) )
938+ } ;
939+
918940 let local_to_root_parent = match root_parent {
919- Some ( parent) => or_continue ! ( path. strip_prefix( & parent) ) . to_path_buf ( ) ,
941+ Some ( parent) => {
942+ #[ cfg( windows) ]
943+ {
944+ // On Windows, some pathes are starting with \\?
945+ // but not always, so, make sure that we are consistent for strip_prefix
946+ // See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file for more info
947+ let parent_can = adjust_canonicalization ( parent) ;
948+ let path_can = adjust_canonicalization ( & path) ;
949+
950+ or_continue ! ( & path_can. strip_prefix( & parent_can) ) . to_path_buf ( )
951+ }
952+ #[ cfg( not( windows) ) ]
953+ {
954+ or_continue ! ( path. strip_prefix( & parent) ) . to_path_buf ( )
955+ }
956+ }
920957 None => path. clone ( ) ,
921958 } ;
922959
@@ -1171,9 +1208,26 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
11711208 ReflinkMode :: Never => { }
11721209 }
11731210 }
1211+ } else if options. no_dereference && fs:: symlink_metadata ( & source) ?. file_type ( ) . is_symlink ( ) {
1212+ // Here, we will copy the symlink itself (actually, just recreate it)
1213+ let link = fs:: read_link ( & source) ?;
1214+ let dest: Cow < ' _ , Path > = if dest. is_dir ( ) {
1215+ match source. file_name ( ) {
1216+ Some ( name) => dest. join ( name) . into ( ) ,
1217+ None => crash ! (
1218+ EXIT_ERR ,
1219+ "cannot stat ‘{}’: No such file or directory" ,
1220+ source. display( )
1221+ ) ,
1222+ }
1223+ } else {
1224+ dest. into ( )
1225+ } ;
1226+ symlink_file ( & link, & dest, & * context_for ( & link, & dest) ) ?;
11741227 } else {
11751228 fs:: copy ( source, dest) . context ( & * context_for ( source, dest) ) ?;
11761229 }
1230+
11771231 Ok ( ( ) )
11781232}
11791233
0 commit comments