@@ -29,8 +29,9 @@ use platform::copy_on_write;
2929use uucore:: display:: Quotable ;
3030use uucore:: error:: { set_exit_code, UClapError , UError , UResult , UUsageError } ;
3131use uucore:: fs:: {
32- are_hardlinks_to_same_file, canonicalize, is_symlink_loop, path_ends_with_terminator,
33- paths_refer_to_same_file, FileInformation , MissingHandling , ResolveMode ,
32+ are_hardlinks_to_same_file, canonicalize, get_filename, is_symlink_loop,
33+ path_ends_with_terminator, paths_refer_to_same_file, FileInformation , MissingHandling ,
34+ ResolveMode ,
3435} ;
3536use uucore:: { backup_control, update_control} ;
3637// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which
@@ -1478,16 +1479,23 @@ pub(crate) fn copy_attributes(
14781479fn symlink_file (
14791480 source : & Path ,
14801481 dest : & Path ,
1481- context : & str ,
14821482 symlinked_files : & mut HashSet < FileInformation > ,
14831483) -> CopyResult < ( ) > {
14841484 #[ cfg( not( windows) ) ]
14851485 {
1486- std:: os:: unix:: fs:: symlink ( source, dest) . context ( context) ?;
1486+ std:: os:: unix:: fs:: symlink ( source, dest) . context ( format ! (
1487+ "cannot create symlink {} to {}" ,
1488+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1489+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1490+ ) ) ?;
14871491 }
14881492 #[ cfg( windows) ]
14891493 {
1490- std:: os:: windows:: fs:: symlink_file ( source, dest) . context ( context) ?;
1494+ std:: os:: windows:: fs:: symlink_file ( source, dest) . context ( format ! (
1495+ "cannot create symlink {} to {}" ,
1496+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1497+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1498+ ) ) ?;
14911499 }
14921500 if let Ok ( file_info) = FileInformation :: from_path ( dest, false ) {
14931501 symlinked_files. insert ( file_info) ;
@@ -1499,10 +1507,11 @@ fn context_for(src: &Path, dest: &Path) -> String {
14991507 format ! ( "{} -> {}" , src. quote( ) , dest. quote( ) )
15001508}
15011509
1502- /// Implements a simple backup copy for the destination file.
1510+ /// Implements a simple backup copy for the destination file .
1511+ /// if is_dest_symlink flag is set to true dest will be renamed to backup_path
15031512/// TODO: for the backup, should this function be replaced by `copy_file(...)`?
1504- fn backup_dest ( dest : & Path , backup_path : & Path ) -> CopyResult < PathBuf > {
1505- if dest . is_symlink ( ) {
1513+ fn backup_dest ( dest : & Path , backup_path : & Path , is_dest_symlink : bool ) -> CopyResult < PathBuf > {
1514+ if is_dest_symlink {
15061515 fs:: rename ( dest, backup_path) ?;
15071516 } else {
15081517 fs:: copy ( dest, backup_path) ?;
@@ -1523,11 +1532,38 @@ fn is_forbidden_to_copy_to_same_file(
15231532) -> bool {
15241533 // TODO To match the behavior of GNU cp, we also need to check
15251534 // that the file is a regular file.
1535+ let source_is_symlink = source. is_symlink ( ) ;
1536+ let dest_is_symlink = dest. is_symlink ( ) ;
1537+ // only disable dereference if both source and dest is symlink and dereference flag is disabled
15261538 let dereference_to_compare =
1527- options. dereference ( source_in_command_line) || !source. is_symlink ( ) ;
1528- paths_refer_to_same_file ( source, dest, dereference_to_compare)
1529- && !( options. force ( ) && options. backup != BackupMode :: NoBackup )
1530- && !( dest. is_symlink ( ) && options. backup != BackupMode :: NoBackup )
1539+ options. dereference ( source_in_command_line) || ( !source_is_symlink || !dest_is_symlink) ;
1540+ if !paths_refer_to_same_file ( source, dest, dereference_to_compare) {
1541+ return false ;
1542+ }
1543+ if options. backup != BackupMode :: NoBackup {
1544+ if options. force ( ) && !source_is_symlink {
1545+ return false ;
1546+ }
1547+ if source_is_symlink && !options. dereference {
1548+ return false ;
1549+ }
1550+ if dest_is_symlink {
1551+ return false ;
1552+ }
1553+ if !dest_is_symlink && !source_is_symlink && dest != source {
1554+ return false ;
1555+ }
1556+ }
1557+ if options. copy_mode == CopyMode :: Link {
1558+ return false ;
1559+ }
1560+ if options. copy_mode == CopyMode :: SymLink && dest_is_symlink {
1561+ return false ;
1562+ }
1563+ if dest_is_symlink && source_is_symlink && !options. dereference {
1564+ return false ;
1565+ }
1566+ true
15311567}
15321568
15331569/// Back up, remove, or leave intact the destination file, depending on the options.
@@ -1536,6 +1572,7 @@ fn handle_existing_dest(
15361572 dest : & Path ,
15371573 options : & Options ,
15381574 source_in_command_line : bool ,
1575+ copied_files : & mut HashMap < FileInformation , PathBuf > ,
15391576) -> CopyResult < ( ) > {
15401577 // Disallow copying a file to itself, unless `--force` and
15411578 // `--backup` are both specified.
@@ -1547,6 +1584,7 @@ fn handle_existing_dest(
15471584 options. overwrite . verify ( dest) ?;
15481585 }
15491586
1587+ let mut is_dest_removed = false ;
15501588 let backup_path = backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
15511589 if let Some ( backup_path) = backup_path {
15521590 if paths_refer_to_same_file ( source, & backup_path, true ) {
@@ -1557,13 +1595,16 @@ fn handle_existing_dest(
15571595 )
15581596 . into ( ) ) ;
15591597 } else {
1560- backup_dest ( dest, & backup_path) ?;
1598+ is_dest_removed = dest. is_symlink ( ) ;
1599+ backup_dest ( dest, & backup_path, is_dest_removed) ?;
15611600 }
15621601 }
15631602 match options. overwrite {
15641603 // FIXME: print that the file was removed if --verbose is enabled
15651604 OverwriteMode :: Clobber ( ClobberMode :: Force ) => {
1566- if is_symlink_loop ( dest) || fs:: metadata ( dest) ?. permissions ( ) . readonly ( ) {
1605+ if !is_dest_removed
1606+ && ( is_symlink_loop ( dest) || fs:: metadata ( dest) ?. permissions ( ) . readonly ( ) )
1607+ {
15671608 fs:: remove_file ( dest) ?;
15681609 }
15691610 }
@@ -1584,7 +1625,19 @@ fn handle_existing_dest(
15841625 // `dest/src/f` and `dest/src/f` has the contents of
15851626 // `src/f`, we delete the existing file to allow the hard
15861627 // linking.
1587- if options. preserve_hard_links ( ) {
1628+
1629+ if options. preserve_hard_links ( )
1630+ // only try to remove dest file only if the current source
1631+ // is hardlink to a file that is already copied
1632+ && copied_files. contains_key (
1633+ & FileInformation :: from_path (
1634+ source,
1635+ options. dereference ( source_in_command_line) ,
1636+ )
1637+ . context ( format ! ( "cannot stat {}" , source. quote( ) ) ) ?,
1638+ )
1639+ && !is_dest_removed
1640+ {
15881641 fs:: remove_file ( dest) ?;
15891642 }
15901643 }
@@ -1710,7 +1763,7 @@ fn handle_copy_mode(
17101763 let backup_path =
17111764 backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
17121765 if let Some ( backup_path) = backup_path {
1713- backup_dest ( dest, & backup_path) ?;
1766+ backup_dest ( dest, & backup_path, dest . is_symlink ( ) ) ?;
17141767 fs:: remove_file ( dest) ?;
17151768 }
17161769 if options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
@@ -1724,7 +1777,11 @@ fn handle_copy_mode(
17241777 } else {
17251778 fs:: hard_link ( source, dest)
17261779 }
1727- . context ( context) ?;
1780+ . context ( format ! (
1781+ "cannot create hard link {} to {}" ,
1782+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1783+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1784+ ) ) ?;
17281785 }
17291786 CopyMode :: Copy => {
17301787 copy_helper (
@@ -1741,7 +1798,7 @@ fn handle_copy_mode(
17411798 if dest. exists ( ) && options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
17421799 fs:: remove_file ( dest) ?;
17431800 }
1744- symlink_file ( source, dest, context , symlinked_files) ?;
1801+ symlink_file ( source, dest, symlinked_files) ?;
17451802 }
17461803 CopyMode :: Update => {
17471804 if dest. exists ( ) {
@@ -1870,8 +1927,10 @@ fn copy_file(
18701927 copied_files : & mut HashMap < FileInformation , PathBuf > ,
18711928 source_in_command_line : bool ,
18721929) -> CopyResult < ( ) > {
1930+ let source_is_symlink = source. is_symlink ( ) ;
1931+ let dest_is_symlink = dest. is_symlink ( ) ;
18731932 // Fail if dest is a dangling symlink or a symlink this program created previously
1874- if dest . is_symlink ( ) {
1933+ if dest_is_symlink {
18751934 if FileInformation :: from_path ( dest, false )
18761935 . map ( |info| symlinked_files. contains ( & info) )
18771936 . unwrap_or ( false )
@@ -1882,7 +1941,7 @@ fn copy_file(
18821941 dest. display( )
18831942 ) ) ) ;
18841943 }
1885- let copy_contents = options. dereference ( source_in_command_line) || !source . is_symlink ( ) ;
1944+ let copy_contents = options. dereference ( source_in_command_line) || !source_is_symlink ;
18861945 if copy_contents
18871946 && !dest. exists ( )
18881947 && !matches ! (
@@ -1908,6 +1967,7 @@ fn copy_file(
19081967 }
19091968
19101969 if are_hardlinks_to_same_file ( source, dest)
1970+ && source != dest
19111971 && matches ! (
19121972 options. overwrite,
19131973 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1923,19 +1983,37 @@ fn copy_file(
19231983 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
19241984 ) )
19251985 {
1926- if are_hardlinks_to_same_file ( source, dest)
1927- && !options. force ( )
1928- && options. backup == BackupMode :: NoBackup
1929- && source != dest
1930- || ( source == dest && options. copy_mode == CopyMode :: Link )
1931- {
1932- return Ok ( ( ) ) ;
1986+ if paths_refer_to_same_file ( source, dest, true ) && options. copy_mode == CopyMode :: Link {
1987+ if source_is_symlink {
1988+ if !dest_is_symlink {
1989+ return Ok ( ( ) ) ;
1990+ }
1991+ if !options. dereference {
1992+ return Ok ( ( ) ) ;
1993+ }
1994+ } else if options. backup != BackupMode :: NoBackup && !dest_is_symlink {
1995+ if source == dest {
1996+ if !options. force ( ) {
1997+ return Ok ( ( ) ) ;
1998+ }
1999+ } else {
2000+ return Ok ( ( ) ) ;
2001+ }
2002+ }
2003+ }
2004+ handle_existing_dest ( source, dest, options, source_in_command_line, copied_files) ?;
2005+ if are_hardlinks_to_same_file ( source, dest) {
2006+ if options. copy_mode == CopyMode :: Copy && options. backup != BackupMode :: NoBackup {
2007+ return Ok ( ( ) ) ;
2008+ }
2009+ if options. copy_mode == CopyMode :: Link && ( !source_is_symlink || !dest_is_symlink) {
2010+ return Ok ( ( ) ) ;
2011+ }
19332012 }
1934- handle_existing_dest ( source, dest, options, source_in_command_line) ?;
19352013 }
19362014
19372015 if options. attributes_only
1938- && source . is_symlink ( )
2016+ && source_is_symlink
19392017 && !matches ! (
19402018 options. overwrite,
19412019 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1991,7 +2069,7 @@ fn copy_file(
19912069 ) ?;
19922070
19932071 // TODO: implement something similar to gnu's lchown
1994- if !dest . is_symlink ( ) {
2072+ if !dest_is_symlink {
19952073 // Here, to match GNU semantics, we quietly ignore an error
19962074 // if a user does not have the correct ownership to modify
19972075 // the permissions of a file.
@@ -2140,7 +2218,7 @@ fn copy_link(
21402218 if dest. is_symlink ( ) || dest. is_file ( ) {
21412219 fs:: remove_file ( dest) ?;
21422220 }
2143- symlink_file ( & link, dest, & context_for ( & link , dest ) , symlinked_files)
2221+ symlink_file ( & link, dest, symlinked_files)
21442222}
21452223
21462224/// Generate an error message if `target` is not the correct `target_type`
0 commit comments