@@ -29,8 +29,9 @@ use platform::copy_on_write;
29
29
use uucore:: display:: Quotable ;
30
30
use uucore:: error:: { set_exit_code, UClapError , UError , UResult , UUsageError } ;
31
31
use 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 ,
34
35
} ;
35
36
use uucore:: { backup_control, update_control} ;
36
37
// 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(
1478
1479
fn symlink_file (
1479
1480
source : & Path ,
1480
1481
dest : & Path ,
1481
- context : & str ,
1482
1482
symlinked_files : & mut HashSet < FileInformation > ,
1483
1483
) -> CopyResult < ( ) > {
1484
1484
#[ cfg( not( windows) ) ]
1485
1485
{
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
+ ) ) ?;
1487
1491
}
1488
1492
#[ cfg( windows) ]
1489
1493
{
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
+ ) ) ?;
1491
1499
}
1492
1500
if let Ok ( file_info) = FileInformation :: from_path ( dest, false ) {
1493
1501
symlinked_files. insert ( file_info) ;
@@ -1499,10 +1507,11 @@ fn context_for(src: &Path, dest: &Path) -> String {
1499
1507
format ! ( "{} -> {}" , src. quote( ) , dest. quote( ) )
1500
1508
}
1501
1509
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
1503
1512
/// 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 {
1506
1515
fs:: rename ( dest, backup_path) ?;
1507
1516
} else {
1508
1517
fs:: copy ( dest, backup_path) ?;
@@ -1523,11 +1532,38 @@ fn is_forbidden_to_copy_to_same_file(
1523
1532
) -> bool {
1524
1533
// TODO To match the behavior of GNU cp, we also need to check
1525
1534
// 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
1526
1538
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
1531
1567
}
1532
1568
1533
1569
/// Back up, remove, or leave intact the destination file, depending on the options.
@@ -1536,6 +1572,7 @@ fn handle_existing_dest(
1536
1572
dest : & Path ,
1537
1573
options : & Options ,
1538
1574
source_in_command_line : bool ,
1575
+ copied_files : & mut HashMap < FileInformation , PathBuf > ,
1539
1576
) -> CopyResult < ( ) > {
1540
1577
// Disallow copying a file to itself, unless `--force` and
1541
1578
// `--backup` are both specified.
@@ -1547,6 +1584,7 @@ fn handle_existing_dest(
1547
1584
options. overwrite . verify ( dest) ?;
1548
1585
}
1549
1586
1587
+ let mut is_dest_removed = false ;
1550
1588
let backup_path = backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
1551
1589
if let Some ( backup_path) = backup_path {
1552
1590
if paths_refer_to_same_file ( source, & backup_path, true ) {
@@ -1557,13 +1595,16 @@ fn handle_existing_dest(
1557
1595
)
1558
1596
. into ( ) ) ;
1559
1597
} else {
1560
- backup_dest ( dest, & backup_path) ?;
1598
+ is_dest_removed = dest. is_symlink ( ) ;
1599
+ backup_dest ( dest, & backup_path, is_dest_removed) ?;
1561
1600
}
1562
1601
}
1563
1602
match options. overwrite {
1564
1603
// FIXME: print that the file was removed if --verbose is enabled
1565
1604
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
+ {
1567
1608
fs:: remove_file ( dest) ?;
1568
1609
}
1569
1610
}
@@ -1584,7 +1625,19 @@ fn handle_existing_dest(
1584
1625
// `dest/src/f` and `dest/src/f` has the contents of
1585
1626
// `src/f`, we delete the existing file to allow the hard
1586
1627
// 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
+ {
1588
1641
fs:: remove_file ( dest) ?;
1589
1642
}
1590
1643
}
@@ -1710,7 +1763,7 @@ fn handle_copy_mode(
1710
1763
let backup_path =
1711
1764
backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
1712
1765
if let Some ( backup_path) = backup_path {
1713
- backup_dest ( dest, & backup_path) ?;
1766
+ backup_dest ( dest, & backup_path, dest . is_symlink ( ) ) ?;
1714
1767
fs:: remove_file ( dest) ?;
1715
1768
}
1716
1769
if options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
@@ -1724,7 +1777,11 @@ fn handle_copy_mode(
1724
1777
} else {
1725
1778
fs:: hard_link ( source, dest)
1726
1779
}
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
+ ) ) ?;
1728
1785
}
1729
1786
CopyMode :: Copy => {
1730
1787
copy_helper (
@@ -1741,7 +1798,7 @@ fn handle_copy_mode(
1741
1798
if dest. exists ( ) && options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
1742
1799
fs:: remove_file ( dest) ?;
1743
1800
}
1744
- symlink_file ( source, dest, context , symlinked_files) ?;
1801
+ symlink_file ( source, dest, symlinked_files) ?;
1745
1802
}
1746
1803
CopyMode :: Update => {
1747
1804
if dest. exists ( ) {
@@ -1870,8 +1927,10 @@ fn copy_file(
1870
1927
copied_files : & mut HashMap < FileInformation , PathBuf > ,
1871
1928
source_in_command_line : bool ,
1872
1929
) -> CopyResult < ( ) > {
1930
+ let source_is_symlink = source. is_symlink ( ) ;
1931
+ let dest_is_symlink = dest. is_symlink ( ) ;
1873
1932
// Fail if dest is a dangling symlink or a symlink this program created previously
1874
- if dest . is_symlink ( ) {
1933
+ if dest_is_symlink {
1875
1934
if FileInformation :: from_path ( dest, false )
1876
1935
. map ( |info| symlinked_files. contains ( & info) )
1877
1936
. unwrap_or ( false )
@@ -1882,7 +1941,7 @@ fn copy_file(
1882
1941
dest. display( )
1883
1942
) ) ) ;
1884
1943
}
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 ;
1886
1945
if copy_contents
1887
1946
&& !dest. exists ( )
1888
1947
&& !matches ! (
@@ -1908,6 +1967,7 @@ fn copy_file(
1908
1967
}
1909
1968
1910
1969
if are_hardlinks_to_same_file ( source, dest)
1970
+ && source != dest
1911
1971
&& matches ! (
1912
1972
options. overwrite,
1913
1973
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1923,19 +1983,37 @@ fn copy_file(
1923
1983
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
1924
1984
) )
1925
1985
{
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
+ }
1933
2012
}
1934
- handle_existing_dest ( source, dest, options, source_in_command_line) ?;
1935
2013
}
1936
2014
1937
2015
if options. attributes_only
1938
- && source . is_symlink ( )
2016
+ && source_is_symlink
1939
2017
&& !matches ! (
1940
2018
options. overwrite,
1941
2019
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1991,7 +2069,7 @@ fn copy_file(
1991
2069
) ?;
1992
2070
1993
2071
// TODO: implement something similar to gnu's lchown
1994
- if !dest . is_symlink ( ) {
2072
+ if !dest_is_symlink {
1995
2073
// Here, to match GNU semantics, we quietly ignore an error
1996
2074
// if a user does not have the correct ownership to modify
1997
2075
// the permissions of a file.
@@ -2140,7 +2218,7 @@ fn copy_link(
2140
2218
if dest. is_symlink ( ) || dest. is_file ( ) {
2141
2219
fs:: remove_file ( dest) ?;
2142
2220
}
2143
- symlink_file ( & link, dest, & context_for ( & link , dest ) , symlinked_files)
2221
+ symlink_file ( & link, dest, symlinked_files)
2144
2222
}
2145
2223
2146
2224
/// Generate an error message if `target` is not the correct `target_type`
0 commit comments