* 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


Reply via email to