@@ -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
@@ -1468,16 +1469,23 @@ pub(crate) fn copy_attributes(
1468
1469
fn symlink_file (
1469
1470
source : & Path ,
1470
1471
dest : & Path ,
1471
- context : & str ,
1472
1472
symlinked_files : & mut HashSet < FileInformation > ,
1473
1473
) -> CopyResult < ( ) > {
1474
1474
#[ cfg( not( windows) ) ]
1475
1475
{
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
+ ) ) ?;
1477
1481
}
1478
1482
#[ cfg( windows) ]
1479
1483
{
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
+ ) ) ?;
1481
1489
}
1482
1490
if let Ok ( file_info) = FileInformation :: from_path ( dest, false ) {
1483
1491
symlinked_files. insert ( file_info) ;
@@ -1489,10 +1497,11 @@ fn context_for(src: &Path, dest: &Path) -> String {
1489
1497
format ! ( "{} -> {}" , src. quote( ) , dest. quote( ) )
1490
1498
}
1491
1499
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
1493
1502
/// 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 {
1496
1505
fs:: rename ( dest, backup_path) ?;
1497
1506
} else {
1498
1507
fs:: copy ( dest, backup_path) ?;
@@ -1513,11 +1522,38 @@ fn is_forbidden_to_copy_to_same_file(
1513
1522
) -> bool {
1514
1523
// TODO To match the behavior of GNU cp, we also need to check
1515
1524
// 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
1516
1528
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
1521
1557
}
1522
1558
1523
1559
/// Back up, remove, or leave intact the destination file, depending on the options.
@@ -1526,6 +1562,7 @@ fn handle_existing_dest(
1526
1562
dest : & Path ,
1527
1563
options : & Options ,
1528
1564
source_in_command_line : bool ,
1565
+ copied_files : & mut HashMap < FileInformation , PathBuf > ,
1529
1566
) -> CopyResult < ( ) > {
1530
1567
// Disallow copying a file to itself, unless `--force` and
1531
1568
// `--backup` are both specified.
@@ -1537,6 +1574,7 @@ fn handle_existing_dest(
1537
1574
options. overwrite . verify ( dest) ?;
1538
1575
}
1539
1576
1577
+ let mut is_dest_removed = false ;
1540
1578
let backup_path = backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
1541
1579
if let Some ( backup_path) = backup_path {
1542
1580
if paths_refer_to_same_file ( source, & backup_path, true ) {
@@ -1547,13 +1585,16 @@ fn handle_existing_dest(
1547
1585
)
1548
1586
. into ( ) ) ;
1549
1587
} else {
1550
- backup_dest ( dest, & backup_path) ?;
1588
+ is_dest_removed = dest. is_symlink ( ) ;
1589
+ backup_dest ( dest, & backup_path, is_dest_removed) ?;
1551
1590
}
1552
1591
}
1553
1592
match options. overwrite {
1554
1593
// FIXME: print that the file was removed if --verbose is enabled
1555
1594
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
+ {
1557
1598
fs:: remove_file ( dest) ?;
1558
1599
}
1559
1600
}
@@ -1574,7 +1615,19 @@ fn handle_existing_dest(
1574
1615
// `dest/src/f` and `dest/src/f` has the contents of
1575
1616
// `src/f`, we delete the existing file to allow the hard
1576
1617
// 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
+ {
1578
1631
fs:: remove_file ( dest) ?;
1579
1632
}
1580
1633
}
@@ -1700,7 +1753,7 @@ fn handle_copy_mode(
1700
1753
let backup_path =
1701
1754
backup_control:: get_backup_path ( options. backup , dest, & options. backup_suffix ) ;
1702
1755
if let Some ( backup_path) = backup_path {
1703
- backup_dest ( dest, & backup_path) ?;
1756
+ backup_dest ( dest, & backup_path, dest . is_symlink ( ) ) ?;
1704
1757
fs:: remove_file ( dest) ?;
1705
1758
}
1706
1759
if options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
@@ -1714,7 +1767,11 @@ fn handle_copy_mode(
1714
1767
} else {
1715
1768
fs:: hard_link ( source, dest)
1716
1769
}
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
+ ) ) ?;
1718
1775
}
1719
1776
CopyMode :: Copy => {
1720
1777
copy_helper (
@@ -1731,7 +1788,7 @@ fn handle_copy_mode(
1731
1788
if dest. exists ( ) && options. overwrite == OverwriteMode :: Clobber ( ClobberMode :: Force ) {
1732
1789
fs:: remove_file ( dest) ?;
1733
1790
}
1734
- symlink_file ( source, dest, context , symlinked_files) ?;
1791
+ symlink_file ( source, dest, symlinked_files) ?;
1735
1792
}
1736
1793
CopyMode :: Update => {
1737
1794
if dest. exists ( ) {
@@ -1860,8 +1917,10 @@ fn copy_file(
1860
1917
copied_files : & mut HashMap < FileInformation , PathBuf > ,
1861
1918
source_in_command_line : bool ,
1862
1919
) -> CopyResult < ( ) > {
1920
+ let source_is_symlink = source. is_symlink ( ) ;
1921
+ let dest_is_symlink = dest. is_symlink ( ) ;
1863
1922
// Fail if dest is a dangling symlink or a symlink this program created previously
1864
- if dest . is_symlink ( ) {
1923
+ if dest_is_symlink {
1865
1924
if FileInformation :: from_path ( dest, false )
1866
1925
. map ( |info| symlinked_files. contains ( & info) )
1867
1926
. unwrap_or ( false )
@@ -1872,7 +1931,7 @@ fn copy_file(
1872
1931
dest. display( )
1873
1932
) ) ) ;
1874
1933
}
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 ;
1876
1935
if copy_contents
1877
1936
&& !dest. exists ( )
1878
1937
&& !matches ! (
@@ -1898,6 +1957,7 @@ fn copy_file(
1898
1957
}
1899
1958
1900
1959
if are_hardlinks_to_same_file ( source, dest)
1960
+ && source != dest
1901
1961
&& matches ! (
1902
1962
options. overwrite,
1903
1963
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1913,19 +1973,37 @@ fn copy_file(
1913
1973
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
1914
1974
) )
1915
1975
{
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
+ }
1923
2002
}
1924
- handle_existing_dest ( source, dest, options, source_in_command_line) ?;
1925
2003
}
1926
2004
1927
2005
if options. attributes_only
1928
- && source . is_symlink ( )
2006
+ && source_is_symlink
1929
2007
&& !matches ! (
1930
2008
options. overwrite,
1931
2009
OverwriteMode :: Clobber ( ClobberMode :: RemoveDestination )
@@ -1981,7 +2059,7 @@ fn copy_file(
1981
2059
) ?;
1982
2060
1983
2061
// TODO: implement something similar to gnu's lchown
1984
- if !dest . is_symlink ( ) {
2062
+ if !dest_is_symlink {
1985
2063
// Here, to match GNU semantics, we quietly ignore an error
1986
2064
// if a user does not have the correct ownership to modify
1987
2065
// the permissions of a file.
@@ -2130,7 +2208,7 @@ fn copy_link(
2130
2208
if dest. is_symlink ( ) || dest. is_file ( ) {
2131
2209
fs:: remove_file ( dest) ?;
2132
2210
}
2133
- symlink_file ( & link, dest, & context_for ( & link , dest ) , symlinked_files)
2211
+ symlink_file ( & link, dest, symlinked_files)
2134
2212
}
2135
2213
2136
2214
/// Generate an error message if `target` is not the correct `target_type`
0 commit comments