@@ -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
@@ -1468,16 +1469,23 @@ pub(crate) fn copy_attributes(
14681469fn symlink_file (
14691470 source : & Path ,
14701471 dest : & Path ,
1471- context : & str ,
14721472 symlinked_files : & mut HashSet < FileInformation > ,
14731473) -> CopyResult < ( ) > {
14741474 #[ cfg( not( windows) ) ]
14751475 {
1476- std:: os:: unix:: fs:: symlink ( source, dest) . context ( context) ?;
1476+ std:: os:: unix:: fs:: symlink ( source, dest) . context ( format ! (
1477+ "cannot create symlink {} to {}" ,
1478+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1479+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1480+ ) ) ?;
14771481 }
14781482 #[ cfg( windows) ]
14791483 {
1480- std:: os:: windows:: fs:: symlink_file ( source, dest) . context ( context) ?;
1484+ std:: os:: windows:: fs:: symlink_file ( source, dest) . context ( format ! (
1485+ "cannot create symlink {} to {}" ,
1486+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1487+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1488+ ) ) ?;
14811489 }
14821490 if let Ok ( file_info) = FileInformation :: from_path ( dest, false ) {
14831491 symlinked_files. insert ( file_info) ;
@@ -1489,10 +1497,11 @@ fn context_for(src: &Path, dest: &Path) -> String {
14891497 format ! ( "{} -> {}" , src. quote( ) , dest. quote( ) )
14901498}
14911499
1492- /// Implements a simple backup copy for the destination file.
1500+ /// Implements a simple backup copy for the destination file .
1501+ /// if is_dest_symlink flag is set to true dest will be renamed to backup_path
14931502/// TODO: for the backup, should this function be replaced by `copy_file(...)`?
1494- fn backup_dest ( dest : & Path , backup_path : & Path ) -> CopyResult < PathBuf > {
1495- if dest . is_symlink ( ) {
1503+ fn backup_dest ( dest : & Path , backup_path : & Path , is_dest_symlink : bool ) -> CopyResult < PathBuf > {
1504+ if is_dest_symlink {
14961505 fs:: rename ( dest, backup_path) ?;
14971506 } else {
14981507 fs:: copy ( dest, backup_path) ?;
@@ -1513,11 +1522,38 @@ fn is_forbidden_to_copy_to_same_file(
15131522) -> bool {
15141523 // TODO To match the behavior of GNU cp, we also need to check
15151524 // that the file is a regular file.
1525+ let source_is_symlink = source. is_symlink ( ) ;
1526+ let dest_is_symlink = dest. is_symlink ( ) ;
1527+ // only disable dereference if both source and dest is symlink and dereference flag is disabled
15161528 let dereference_to_compare =
1517- options. dereference ( source_in_command_line) || !source. is_symlink ( ) ;
1518- paths_refer_to_same_file ( source, dest, dereference_to_compare)
1519- && !( options. force ( ) && options. backup != BackupMode :: NoBackup )
1520- && !( dest. is_symlink ( ) && options. backup != BackupMode :: NoBackup )
1529+ options. dereference ( source_in_command_line) || ( !source_is_symlink || !dest_is_symlink) ;
1530+ if !paths_refer_to_same_file ( source, dest, dereference_to_compare) {
1531+ return false ;
1532+ }
1533+ if options. backup != BackupMode :: NoBackup {
1534+ if options. force ( ) && !source_is_symlink {
1535+ return false ;
1536+ }
1537+ if source_is_symlink && !options. dereference {
1538+ return false ;
1539+ }
1540+ if dest_is_symlink {
1541+ return false ;
1542+ }
1543+ if !dest_is_symlink && !source_is_symlink && dest != source {
1544+ return false ;
1545+ }
1546+ }
1547+ if options. copy_mode == CopyMode :: Link {
1548+ return false ;
1549+ }
1550+ if options. copy_mode == CopyMode :: SymLink && dest_is_symlink {
1551+ return false ;
1552+ }
1553+ if dest_is_symlink && source_is_symlink && !options. dereference {
1554+ return false ;
1555+ }
1556+ true
15211557}
15221558
15231559/// Back up, remove, or leave intact the destination file, depending on the options.
@@ -1526,6 +1562,7 @@ fn handle_existing_dest(
15261562 dest : & Path ,
15271563 options : & Options ,
15281564 source_in_command_line : bool ,
1565+ copied_files : & mut HashMap < FileInformation , PathBuf > ,
15291566) -> CopyResult < ( ) > {
15301567 // Disallow copying a file to itself, unless `--force` and
15311568 // `--backup` are both specified.
@@ -1537,6 +1574,7 @@ fn handle_existing_dest(
15371574 options. overwrite . verify ( dest) ?;
15381575 }
15391576
1577+ let mut is_dest_removed = false ;
15401578 let backup_path = backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
15411579 if let Some ( backup_path) = backup_path {
15421580 if paths_refer_to_same_file ( source, & backup_path, true ) {
@@ -1547,13 +1585,16 @@ fn handle_existing_dest(
15471585 )
15481586 . into ( ) ) ;
15491587 } else {
1550- backup_dest ( dest, & backup_path) ?;
1588+ is_dest_removed = dest. is_symlink ( ) ;
1589+ backup_dest ( dest, & backup_path, is_dest_removed) ?;
15511590 }
15521591 }
15531592 match options. overwrite {
15541593 // FIXME: print that the file was removed if --verbose is enabled
15551594 OverwriteMode :: Clobber ( ClobberMode :: Force ) => {
1556- if is_symlink_loop ( dest) || fs:: metadata ( dest) ?. permissions ( ) . readonly ( ) {
1595+ if !is_dest_removed
1596+ && ( is_symlink_loop ( dest) || fs:: metadata ( dest) ?. permissions ( ) . readonly ( ) )
1597+ {
15571598 fs:: remove_file ( dest) ?;
15581599 }
15591600 }
@@ -1574,7 +1615,19 @@ fn handle_existing_dest(
15741615 // `dest/src/f` and `dest/src/f` has the contents of
15751616 // `src/f`, we delete the existing file to allow the hard
15761617 // linking.
1577- if options. preserve_hard_links ( ) {
1618+
1619+ if options. preserve_hard_links ( )
1620+ // only try to remove dest file only if the current source
1621+ // is hardlink to a file that is already copied
1622+ && copied_files. contains_key (
1623+ & FileInformation :: from_path (
1624+ source,
1625+ options. dereference ( source_in_command_line) ,
1626+ )
1627+ . context ( format ! ( "cannot stat {}" , source. quote( ) ) ) ?,
1628+ )
1629+ && !is_dest_removed
1630+ {
15781631 fs:: remove_file ( dest) ?;
15791632 }
15801633 }
@@ -1700,7 +1753,7 @@ fn handle_copy_mode(
17001753 let backup_path =
17011754 backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
17021755 if let Some ( backup_path) = backup_path {
1703- backup_dest ( dest, & backup_path) ?;
1756+ backup_dest ( dest, & backup_path, dest . is_symlink ( ) ) ?;
17041757 fs:: remove_file ( dest) ?;
17051758 }
17061759 if options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
@@ -1714,7 +1767,11 @@ fn handle_copy_mode(
17141767 } else {
17151768 fs:: hard_link ( source, dest)
17161769 }
1717- . context ( context) ?;
1770+ . context ( format ! (
1771+ "cannot create hard link {} to {}" ,
1772+ get_filename( dest) . unwrap_or( "invalid file name" ) . quote( ) ,
1773+ get_filename( source) . unwrap_or( "invalid file name" ) . quote( )
1774+ ) ) ?;
17181775 }
17191776 CopyMode :: Copy => {
17201777 copy_helper (
@@ -1731,7 +1788,7 @@ fn handle_copy_mode(
17311788 if dest. exists ( ) && options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
17321789 fs:: remove_file ( dest) ?;
17331790 }
1734- symlink_file ( source, dest, context , symlinked_files) ?;
1791+ symlink_file ( source, dest, symlinked_files) ?;
17351792 }
17361793 CopyMode :: Update => {
17371794 if dest. exists ( ) {
@@ -1860,8 +1917,10 @@ fn copy_file(
18601917 copied_files : & mut HashMap < FileInformation , PathBuf > ,
18611918 source_in_command_line : bool ,
18621919) -> CopyResult < ( ) > {
1920+ let source_is_symlink = source. is_symlink ( ) ;
1921+ let dest_is_symlink = dest. is_symlink ( ) ;
18631922 // Fail if dest is a dangling symlink or a symlink this program created previously
1864- if dest . is_symlink ( ) {
1923+ if dest_is_symlink {
18651924 if FileInformation :: from_path ( dest, false )
18661925 . map ( |info| symlinked_files. contains ( & info) )
18671926 . unwrap_or ( false )
@@ -1872,7 +1931,7 @@ fn copy_file(
18721931 dest. display( )
18731932 ) ) ) ;
18741933 }
1875- let copy_contents = options. dereference ( source_in_command_line) || !source . is_symlink ( ) ;
1934+ let copy_contents = options. dereference ( source_in_command_line) || !source_is_symlink ;
18761935 if copy_contents
18771936 && !dest. exists ( )
18781937 && !matches ! (
@@ -1898,6 +1957,7 @@ fn copy_file(
18981957 }
18991958
19001959 if are_hardlinks_to_same_file ( source, dest)
1960+ && source != dest
19011961 && matches ! (
19021962 options. overwrite,
19031963 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1913,19 +1973,37 @@ fn copy_file(
19131973 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
19141974 ) )
19151975 {
1916- if are_hardlinks_to_same_file ( source, dest)
1917- && !options. force ( )
1918- && options. backup == BackupMode :: NoBackup
1919- && source != dest
1920- || ( source == dest && options. copy_mode == CopyMode :: Link )
1921- {
1922- return Ok ( ( ) ) ;
1976+ if paths_refer_to_same_file ( source, dest, true ) && options. copy_mode == CopyMode :: Link {
1977+ if source_is_symlink {
1978+ if !dest_is_symlink {
1979+ return Ok ( ( ) ) ;
1980+ }
1981+ if !options. dereference {
1982+ return Ok ( ( ) ) ;
1983+ }
1984+ } else if options. backup != BackupMode :: NoBackup && !dest_is_symlink {
1985+ if source == dest {
1986+ if !options. force ( ) {
1987+ return Ok ( ( ) ) ;
1988+ }
1989+ } else {
1990+ return Ok ( ( ) ) ;
1991+ }
1992+ }
1993+ }
1994+ handle_existing_dest ( source, dest, options, source_in_command_line, copied_files) ?;
1995+ if are_hardlinks_to_same_file ( source, dest) {
1996+ if options. copy_mode == CopyMode :: Copy && options. backup != BackupMode :: NoBackup {
1997+ return Ok ( ( ) ) ;
1998+ }
1999+ if options. copy_mode == CopyMode :: Link && ( !source_is_symlink || !dest_is_symlink) {
2000+ return Ok ( ( ) ) ;
2001+ }
19232002 }
1924- handle_existing_dest ( source, dest, options, source_in_command_line) ?;
19252003 }
19262004
19272005 if options. attributes_only
1928- && source . is_symlink ( )
2006+ && source_is_symlink
19292007 && !matches ! (
19302008 options. overwrite,
19312009 OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1981,7 +2059,7 @@ fn copy_file(
19812059 ) ?;
19822060
19832061 // TODO: implement something similar to gnu's lchown
1984- if !dest . is_symlink ( ) {
2062+ if !dest_is_symlink {
19852063 // Here, to match GNU semantics, we quietly ignore an error
19862064 // if a user does not have the correct ownership to modify
19872065 // the permissions of a file.
@@ -2130,7 +2208,7 @@ fn copy_link(
21302208 if dest. is_symlink ( ) || dest. is_file ( ) {
21312209 fs:: remove_file ( dest) ?;
21322210 }
2133- symlink_file ( & link, dest, & context_for ( & link , dest ) , symlinked_files)
2211+ symlink_file ( & link, dest, symlinked_files)
21342212}
21352213
21362214/// Generate an error message if `target` is not the correct `target_type`
0 commit comments