* src/copy-file-data.c (sparse_copy): Return -1 when create_hole fails.
* tests/cp/fail-create-hole.sh: New file.
* tests/local.mk (all_tests): Add the new test case.
* NEWS: Mention the bug fix.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-C18OTMQ2.
---
NEWS | 4 +++
src/copy-file-data.c | 2 +-
tests/cp/fail-create-hole.sh | 47 ++++++++++++++++++++++++++++++++++++
tests/local.mk | 1 +
4 files changed, 53 insertions(+), 1 deletion(-)
create mode 100644 tests/cp/fail-create-hole.sh
diff --git a/NEWS b/NEWS
index b271856c8..e3a23fe54 100644
--- a/NEWS
+++ b/NEWS
@@ -62,6 +62,10 @@ GNU coreutils NEWS -*-
outline -*-
it creates and their sources.
[This bug was present in "the beginning".]
+ 'cp', 'mv', and 'install' no longer exit successfully when they fail to
create
+ a hole in the destination file.
+ [bug introduced in coreutils-9.8]
+
'date -u MMDDhhmm' no longer uses the local time zone's UTC offset when
setting the date and time.
[bug introduced in coreutils-9.11]
diff --git a/src/copy-file-data.c b/src/copy-file-data.c
index e7e3108af..e66749f65 100644
--- a/src/copy-file-data.c
+++ b/src/copy-file-data.c
@@ -236,7 +236,7 @@ sparse_copy (int src_fd, int dest_fd, char **abuf, idx_t
buf_size,
else if (prev_hole)
{
if (create_hole (dest_fd, dst_name, psize) < 0)
- return false;
+ return -1;
pbuf = cbuf;
psize = csize;
}
diff --git a/tests/cp/fail-create-hole.sh b/tests/cp/fail-create-hole.sh
new file mode 100644
index 000000000..941b24734
--- /dev/null
+++ b/tests/cp/fail-create-hole.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+# Test that 'cp' behaves correctly when failing to create a hole.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ cp
+require_sparse_support_
+require_strace_ fallocate
+getlimits_
+
+fail_fallocate ()
+{
+ strace --quiet=all -o trace -e inject=fallocate:error=EIO "$@" >out 2>err
+}
+
+# Make sure we can inject an fallocate failure.
+fail_fallocate true || skip_ 'strace does not support the required options'
+
+# Create a file starting with "a", followed by zeros and then "b".
+{ printf a && dd if=/dev/zero bs=256k count=1 status=none && printf b; } >in &&
+echo "cp: error deallocating 'copy': $EIO" >exp-err || framework_failure_
+
+fail_fallocate cp --reflink=never --sparse=always in copy
+rc=$?
+
+grep 'EIO .*(INJECTED)' trace || skip_ 'cannot intercept fallocate'
+
+test $rc -eq 1 || fail=1
+compare /dev/null out || fail=1
+compare exp-err err || fail=1
+test -f copy || fail=1
+
+Exit $fail
diff --git a/tests/local.mk b/tests/local.mk
index 7fba68215..8bb7a2ac0 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -605,6 +605,7 @@ all_tests = \
tests/cp/dir-vs-file.sh \
tests/cp/existing-perm-dir.sh \
tests/cp/existing-perm-race.sh \
+ tests/cp/fail-create-hole.sh \
tests/cp/fail-perm.sh \
tests/cp/keep-directory-symlink.sh \
tests/cp/sparse-extents.sh \
--
2.56.0