@@ -1936,8 +1936,7 @@ pub(crate) fn copy_attributes(
19361936fn symlink_file (
19371937 source : & Path ,
19381938 dest : & Path ,
1939- #[ cfg( windows) ] source_type_hint : Option < & Path > ,
1940- #[ cfg( not( windows) ) ] _source_type_hint : Option < & Path > ,
1939+ #[ cfg( windows) ] source_metadata : & Metadata ,
19411940 #[ cfg( not( target_os = "wasi" ) ) ] symlinked_files : & mut HashSet < FileInformation > ,
19421941 #[ cfg( target_os = "wasi" ) ] _symlinked_files : & mut HashSet < FileInformation > ,
19431942) -> CopyResult < ( ) > {
@@ -1963,7 +1962,7 @@ fn symlink_file(
19631962 }
19641963 #[ cfg( windows) ]
19651964 {
1966- let create_symlink = if source_type_hint . is_some_and ( source_is_directory_for_symlink) {
1965+ let create_symlink = if source_is_directory_for_symlink ( source_metadata ) {
19671966 std:: os:: windows:: fs:: symlink_dir
19681967 } else {
19691968 std:: os:: windows:: fs:: symlink_file
@@ -1987,18 +1986,12 @@ fn symlink_file(
19871986 }
19881987}
19891988
1989+ /// Returns true when `source_metadata` should be recreated as a directory symlink.
19901990#[ cfg( windows) ]
1991- fn source_is_directory_for_symlink ( source : & Path ) -> bool {
1991+ fn source_is_directory_for_symlink ( source_metadata : & Metadata ) -> bool {
19921992 const FILE_ATTRIBUTE_DIRECTORY : u32 = 0x10 ;
19931993
1994- source. symlink_metadata ( ) . is_ok_and ( |metadata| {
1995- let file_type = metadata. file_type ( ) ;
1996- if file_type. is_symlink ( ) {
1997- metadata. file_attributes ( ) & FILE_ATTRIBUTE_DIRECTORY != 0
1998- } else {
1999- file_type. is_dir ( )
2000- }
2001- } )
1994+ source_metadata. file_attributes ( ) & FILE_ATTRIBUTE_DIRECTORY != 0
20021995}
20031996
20041997fn context_for ( src : & Path , dest : & Path ) -> String {
@@ -2346,7 +2339,13 @@ fn handle_copy_mode(
23462339 if dest. exists ( ) && options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
23472340 fs:: remove_file ( dest) ?;
23482341 }
2349- symlink_file ( source, dest, Some ( source) , symlinked_files) ?;
2342+ symlink_file (
2343+ source,
2344+ dest,
2345+ #[ cfg( windows) ]
2346+ source_metadata,
2347+ symlinked_files,
2348+ ) ?;
23502349 }
23512350 CopyMode :: Update => {
23522351 if dest. exists ( ) {
@@ -2821,7 +2820,14 @@ fn copy_helper(
28212820 }
28222821
28232822 if source_metadata. is_symlink ( ) {
2824- copy_link ( source, dest, symlinked_files, options) ?;
2823+ copy_link (
2824+ source,
2825+ dest,
2826+ #[ cfg( windows) ]
2827+ source_metadata,
2828+ symlinked_files,
2829+ options,
2830+ ) ?;
28252831 } else {
28262832 // Use O_NOFOLLOW on the source open iff cp is in no-dereference mode.
28272833 // In that case source_metadata was obtained via lstat, so a path swap
@@ -2900,6 +2906,7 @@ fn copy_node(
29002906fn copy_link (
29012907 source : & Path ,
29022908 dest : & Path ,
2909+ #[ cfg( windows) ] source_metadata : & Metadata ,
29032910 symlinked_files : & mut HashSet < FileInformation > ,
29042911 options : & Options ,
29052912) -> CopyResult < ( ) > {
@@ -2910,7 +2917,13 @@ fn copy_link(
29102917 if dest. is_symlink ( ) || dest. is_file ( ) {
29112918 delete_path ( dest, options) ?;
29122919 }
2913- symlink_file ( & link, dest, Some ( source) , symlinked_files) ?;
2920+ symlink_file (
2921+ & link,
2922+ dest,
2923+ #[ cfg( windows) ]
2924+ source_metadata,
2925+ symlinked_files,
2926+ ) ?;
29142927 copy_attributes (
29152928 source,
29162929 dest,
@@ -2986,6 +2999,8 @@ fn disk_usage_directory(p: &Path) -> io::Result<u64> {
29862999#[ cfg( test) ]
29873000mod tests {
29883001
3002+ #[ cfg( windows) ]
3003+ use crate :: source_is_directory_for_symlink;
29893004 use crate :: { Attributes , Preserve , aligned_ancestors, localize_to_target} ;
29903005 use std:: path:: Path ;
29913006
@@ -3056,4 +3071,36 @@ mod tests {
30563071 }
30573072 ) ;
30583073 }
3074+
3075+ #[ test]
3076+ #[ cfg( windows) ]
3077+ fn test_source_is_directory_for_symlink_detects_directory_symlinks ( ) {
3078+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
3079+ let target = temp_dir. path ( ) . join ( "target" ) ;
3080+ let symlink = temp_dir. path ( ) . join ( "target-link" ) ;
3081+
3082+ std:: fs:: create_dir ( & target) . unwrap ( ) ;
3083+ if std:: os:: windows:: fs:: symlink_dir ( & target, & symlink) . is_err ( ) {
3084+ return ;
3085+ }
3086+
3087+ let metadata = symlink. symlink_metadata ( ) . unwrap ( ) ;
3088+ assert ! ( source_is_directory_for_symlink( & metadata) ) ;
3089+ }
3090+
3091+ #[ test]
3092+ #[ cfg( windows) ]
3093+ fn test_source_is_directory_for_symlink_rejects_file_symlinks ( ) {
3094+ let temp_dir = tempfile:: tempdir ( ) . unwrap ( ) ;
3095+ let target = temp_dir. path ( ) . join ( "target" ) ;
3096+ let symlink = temp_dir. path ( ) . join ( "target-link" ) ;
3097+
3098+ std:: fs:: write ( & target, b"data" ) . unwrap ( ) ;
3099+ if std:: os:: windows:: fs:: symlink_file ( & target, & symlink) . is_err ( ) {
3100+ return ;
3101+ }
3102+
3103+ let metadata = symlink. symlink_metadata ( ) . unwrap ( ) ;
3104+ assert ! ( !source_is_directory_for_symlink( & metadata) ) ;
3105+ }
30593106}
0 commit comments