Collin Funk <[email protected]> writes:
> Thanks for the detailed report!
>
> I can reproduce it on coreutils-9.12 as well. I'll take a look at it
> later today.
This patch should do the trick. The issue also occurs with target
directories, i.e., the following commands:
$ touch a; mkdir -p b
$ mv --exchange a b
mv: cannot exchange 'a' and 'b/a': Unknown error -1
$ mv --exchange --target-directory=b a
mv: cannot exchange 'a' and 'b/a': Unknown error -1
So, I've added test cases for those and the more common invocation that
you shared.
I'll push it tomorrow to allow others a chance to review.
Thanks again for the report,
Collin
>From 0841f4875ff1442f217f9352271d7ed64c646a12 Mon Sep 17 00:00:00 2001
Message-ID: <0841f4875ff1442f217f9352271d7ed64c646a12.1793847375.git.collin.fu...@gmail.com>
From: Collin Funk <[email protected]>
Date: Wed, 4 Nov 2026 18:42:54 -0800
Subject: [PATCH] mv: don't lose EEXIST errors when --exchange is used
* THANKS.in: Add Igor Giamoniano, who reported the bug.
* src/copy.c (copy_internal): Save the EEXIST errno when stat fails,
since renameat would fail for the same reason.
* tests/mv/mv-exchange.sh: Add a few test cases where the bug could be
seen.
* NEWS: Mention the bug fix.
Fixes https://bugs.gnu.org/82186
---
NEWS | 5 +++++
THANKS.in | 1 +
src/copy.c | 9 ++++++++-
tests/mv/mv-exchange.sh | 21 +++++++++++++++++++--
4 files changed, 33 insertions(+), 3 deletions(-)
diff --git a/NEWS b/NEWS
index a856efff8..ae5c8e32a 100644
--- a/NEWS
+++ b/NEWS
@@ -110,6 +110,11 @@ GNU coreutils NEWS -*- outline -*-
just-moved FIFO.
[bug introduced with SELinux support in coreutils-8.22]
+ 'mv --exchange A B' now emits a more meaningful error message when file B does
+ not exist. Previously, it would print the error message as if errno were set
+ to -1, e.g., "Unknown error -1" on glibc.
+ [bug introduced with the --exchange option in coreutils-9.5]
+
'nl -d ""' no longer mistakenly treats empty lines as headers.
[bugs introduced in coreutils-9.11]
diff --git a/THANKS.in b/THANKS.in
index 94669125b..3704a3bf8 100644
--- a/THANKS.in
+++ b/THANKS.in
@@ -264,6 +264,7 @@ Ian Jackson [email protected]
Ian Kent [email protected]
Ian Lance Taylor [email protected]
Ian Turner [email protected]
+Igor Giamoniano [email protected]
Iida Yosiaki [email protected]
Illia Bobyr [email protected]
Ilya N. Golubev [email protected]
diff --git a/src/copy.c b/src/copy.c
index cf9be7389..cfd9deaad 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -1748,7 +1748,14 @@ copy_internal (char const *src_name, char const *dst_name,
rename_errno = EEXIST;
}
else if (errno == ENOENT)
- new_dst = true;
+ {
+ /* Using renameatu would fail with ENOENT as well.
+ Prefer the error message describing the failed rename
+ instead of the failed stat. */
+ if (x->exchange)
+ rename_errno = ENOENT;
+ new_dst = true;
+ }
else if (errno == ELOOP && !use_lstat
&& x->unlink_dest_after_failed_open)
{
diff --git a/tests/mv/mv-exchange.sh b/tests/mv/mv-exchange.sh
index 63348a1ec..cd237dcb0 100755
--- a/tests/mv/mv-exchange.sh
+++ b/tests/mv/mv-exchange.sh
@@ -36,7 +36,24 @@ touch c || framework_failure_
returns_ 1 mv --exchange a 2>/dev/null || fail=1
returns_ 1 mv --exchange a b c 2>/dev/null || fail=1
-# Both files must exist.
-returns_ 1 mv --exchange a d 2>/dev/null || fail=1
+# Both files must exist. Check the behavior with a missing destination.
+echo "mv: cannot exchange 'b' and 'd': $ENOENT" >exp-err || framework_failure_
+returns_ 1 mv --exchange b d >out 2>err || fail=1
+compare /dev/null out || fail=1
+compare exp-err err || fail=1
+
+# Check the behavior with a missing source.
+echo "mv: cannot stat 'd': $ENOENT" >exp-err || framework_failure_
+returns_ 1 mv --exchange d b >out 2>err || fail=1
+compare /dev/null out || fail=1
+compare exp-err err || fail=1
+
+# Test the behavior with a missing destination in a target directory.
+echo "mv: cannot exchange 'b' and 'a/b': $ENOENT" >exp-err || framework_failure_
+for opt in '' --target-directory=; do
+ returns_ 1 mv --exchange b ${opt}a >out 2>err || fail=1
+ compare /dev/null out || fail=1
+ compare exp-err err || fail=1
+done
Exit $fail
--
2.56.0