summaryrefslogtreecommitdiff
path: root/src/copy.c
diff options
context:
space:
mode:
authorJim Meyering <meyering@redhat.com>2012-02-01 21:42:45 +0100
committerJim Meyering <meyering@redhat.com>2012-02-12 12:21:53 +0100
commitb01355a1dc4939a3967ee82ac0a6dd19702778c3 (patch)
treea8f6534979bebdda6d60a5985d9a7094322ae95b /src/copy.c
parent31eeead93842c6581e0a9b21f6e8eb964d0c9309 (diff)
downloadcoreutils-b01355a1dc4939a3967ee82ac0a6dd19702778c3.tar.xz
mv: "mv A B" would sometimes succeed, yet A would remain, ...
But only when both A and B were hard links to the same symlink. * src/copy.c (same_file_ok): Handle another special case: the one in which we are moving a symlink onto a hard link to itself. In this case, we must explicitly tell the caller to unlink the source file. Otherwise, at least the linux-3.x kernel rename function would do nothing, as mandated by POSIX 2008. * tests/mv/symlink-onto-hardlink-to-self: New test. * tests/Makefile.am (TESTS): Add it. * NEWS (Bug fixes): Mention it. Reported by Bernhard Voelker in http://bugs.gnu.org/10686
Diffstat (limited to 'src/copy.c')
-rw-r--r--src/copy.c29
1 files changed, 26 insertions, 3 deletions
diff --git a/src/copy.c b/src/copy.c
index 4810de872..541ca20cb 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1226,10 +1226,33 @@ same_file_ok (char const *src_name, struct stat const *src_sb,
same_link = same;
/* If both the source and destination files are symlinks (and we'll
- know this here IFF preserving symlinks), then it's ok -- as long
- as they are distinct. */
+ know this here IFF preserving symlinks), then it's usually ok
+ when they are distinct. */
if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
- return ! same_name (src_name, dst_name);
+ {
+ bool sn = same_name (src_name, dst_name);
+ if ( ! sn)
+ {
+ /* It's fine when we're making any type of backup. */
+ if (x->backup_type != no_backups)
+ return true;
+
+ /* Here we have two symlinks that are hard-linked together,
+ and we're not making backups. In this unusual case, simply
+ returning true would lead to mv calling "rename(A,B)",
+ which would do nothing and return 0. I.e., A would
+ not be removed. Hence, the solution is to tell the
+ caller that all it must do is unlink A and return. */
+ if (same_link)
+ {
+ *unlink_src = true;
+ *return_now = true;
+ return true;
+ }
+ }
+
+ return ! sn;
+ }
src_sb_link = src_sb;
dst_sb_link = dst_sb;