On 2026-09-02 15:00, Pádraig Brady wrote:
On 02/09/2026 20:47, Paul Eggert wrote:
Don't we need to be careful with some errors as per
https://github.com/coreutils/coreutils/commit/f6c93f334
https://bugs.gnu.org/60489 ?
Thanks for bringing that up; I'd forgotten that. Unfortunately, though, bug#60489 seems
to have given us some incorrect advice. In it, Noah Misch wrote "One could argue
that ENOSPC also warrants termination, since no fallback reduces space usage." - but
in this thread people are saying that the fallback read+write can work even when
ioctl+FICLONE fails with ENOSPC.
Also, Noah's bug#60489 comments about EIO suggest that when EIO occurs, the
entire file or maybe even the entire file system (not just this open file
descriptor) is at risk of being inconsistent, which is a kernel bug that we
can't reasonably expect cp to work around.
All that being said, it does seem prudent to go back to having cp give up on
the file entirely (not just revert to read+write) if FICLONE fails with EIO. I
did that by installing the attached.
Also this removes partially cloned files on error,
rather than just empty files. ?
Why keep partially cloned files that we just created? ioctl + FICLONE is
supposed to be atomic, and if a kernel bug makes it non-atomic, the removal is
supposed to work around the bug. Why should the workaround go to the extra work
of checking that the file is empty before unlinking it? That loses the pretense
of atomicity that the workaround should strive for. And particularly with EIO,
the file is in a possibly-inconsistent state so leaving it around could be
hazardous.
I'm a little worried about about giving worrying errors here
rather than an abstract "unsupported". Perhaps map is_CLONENOTSUP()
to "unsupported" and others to their errno?
--debug is *supposed* to worry people (:-), and I thought it better to put a
bit more info into the debugging output. But please feel free to adjust the
--debug diagnostics, though I'm not sure is_CLONENOTSUP is completely apropos
for doing so as is_CLONENOTSUP (which is not aptly named any more) is about
copy_file_range, not about ioctl with FICLONE.
From 1ee46ac2c990a6a3b10737d59d8cb358b59af5df Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Thu, 3 Sep 2026 00:52:39 -0700
Subject: [PATCH] cp: ioctl+FICLONE EIO now fails without fallback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Problem reported by Pádraig Brady (bug#81776#16).
* src/copy.c (handle_clone_fail): Be more cautious if
ioctl+FICLONE failed with EIO; do not fall back to read+write,
even if --reflink=auto is in effect.
---
NEWS | 2 +-
src/copy.c | 20 +++++++++++++-------
2 files changed, 14 insertions(+), 8 deletions(-)
diff --git a/NEWS b/NEWS
index 0e4ad1d94..d0d765523 100644
--- a/NEWS
+++ b/NEWS
@@ -13,7 +13,7 @@ GNU coreutils NEWS -*- outline -*-
[This bug was present in "the beginning".]
'cp`, 'install', and 'mv' now fall back to a standard copy
- if a --reflink=auto clone fails for any reason, including ENOSPC.
+ if a --reflink=auto clone fails due to EDQUOT, ENOMEM, or ENOSPC.
E.g., with XFS, a clone can exhaust metadata space in an allocation
group, while a standard copy works.
[bug introduced in coreutils-9.2]
diff --git a/src/copy.c b/src/copy.c
index ce6aa8e1a..aedbd0dc3 100644
--- a/src/copy.c
+++ b/src/copy.c
@@ -673,25 +673,31 @@ handle_clone_fail (int dst_dirfd, char const *dst_relname,
char const *src_name, char const *dst_name,
bool new_dst, enum Reflink_type reflink_mode)
{
- /* Record failure for debugging, but return false only if --reflink=always.
- No errno value is serious enough to give up on read+write copying,
+ /* Record errno for debugging, but diagnose the failure and return
+ false only if either --reflink=always, or errno is so serious
+ that we should not fall back on read+write copying,
which can succeed even if cloning fails due to ENOSPC etc.
E.g., XFS has Allocation Groups where a clone may fail but a copy
- (to other groups) may succeed. */
+ (to other groups) may succeed. The only known potentially
+ serious errno value is EIO, due to Linux kernel bugs reported
+ in 2023 <https://bugs.gnu.org/60489>. */
copy_debug.reflink = errno;
-
- if (reflink_mode != REFLINK_ALWAYS)
+ if (! (reflink_mode == REFLINK_ALWAYS || errno == EIO))
return true;
error (0, errno, _("failed to clone %s from %s"),
quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
- /* Remove the destination if cp --reflink=always created it
- but cloned no data. */
+ /* Remove the destination if cloning created it but later failed,
+ either due to EIO, or to any error if --reflink=always. The need
+ for removal is a Linux kernel bug, as ioctl with FICLONE is
+ supposed to be atomic. We don't know whether currently supported
+ kernels have the problem, so play it safe. */
if (new_dst /* currently not for fclonefileat(). */
&& unlinkat (dst_dirfd, dst_relname, 0) < 0 && errno != ENOENT)
error (0, errno, _("cannot remove %s"), quoteaf (dst_name));
+
return false;
}
--
2.53.0