Subject: bug#61386: [PATCH] cp,mv,install: Disable sparse copy on macOS

From: Paul Eggert <[email-masked]>
Date: Sun, 19 Feb 2023 00:08:53 -0600

George, given what you've written I suppose we should give up the idea 
of copying sparse files efficiently on macOS (and on FreeBSD 
13.0-RELEASE, as it has a similar bug with SEEK_HOLE and SEEK_DATA), in 
cases where fclonefileat does not work.

Please try the attached Gnulib patch; it uses your idea of disabling 
SEEK_HOLE and SEEK_DATA on macOS, except on steroids because it disables 
these two macros everywhere, for all Gnulib-using applications, and it 
also disables them on FreeBSD < 14. For coreutils you need only this 
patch's change to lib/unistd.in.h. If this works for you I suppose we 
can install it into Gnulib and propagate that into coreutils etc.

Also, you reported the bug against macOS 12.6. Can you check whether the 
same bug occurs on macOS 13? If it's still there I suppose the attached 
patch will need to be updated since it guesses the bug is fixed in macOS 13.

I'll cc this email to bug-gnulib to give them a heads-up. (Gnulib 
readers can see <https://bugs.gnu.org/61386> for context.)

Really, Apple should fix this serious data corruption bug in APFS and macOS.
From 0024b8460dfe5e133f9e0d63ed4f75314dea2f82 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Sun, 19 Feb 2023 00:05:24 -0600
Subject: [PATCH] lseek: avoid SEEK_HOLE bugs in FreeBSD, macOS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This attempts to fix <https://bugs.gnu.org/61386>, a bug in GNU cp
caused by a serious data corruption bug in FreeBSD and macOS.
* doc/posix-functions/lseek.texi: Mention the bug.
* lib/unistd.in.h (SEEK_DATA, SEEK_HOLE): Undef in macOS < 13 and
FreeBSD < 14.  FreeBSD fixed the bug sometime during FreeBSD 13
<https://bugs.freebsd.org/256205>, so the "FreeBSD < 14" is
conservative.  It’s unknown when (or indeed whether) Apple fixed
macOS but this patch guesses macOS 13.
---
 ChangeLog                      | 12 ++++++++++++
 doc/posix-functions/lseek.texi |  5 +++++
 lib/unistd.in.h                | 12 ++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/ChangeLog b/ChangeLog
index c1ca610548..c8e9726916 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2023-02-18  Paul Eggert  <[email-masked]>
+
+	lseek: avoid SEEK_HOLE bugs in FreeBSD, macOS
+	This attempts to fix <https://bugs.gnu.org/61386>, a bug in GNU cp
+	caused by a serious data corruption bug in FreeBSD and macOS.
+	* doc/posix-functions/lseek.texi: Mention the bug.
+	* lib/unistd.in.h (SEEK_DATA, SEEK_HOLE): Undef in macOS < 13 and
+	FreeBSD < 14.  FreeBSD fixed the bug sometime during FreeBSD 13
+	<https://bugs.freebsd.org/256205>, so the "FreeBSD < 14" is
+	conservative.  It’s unknown when (or indeed whether) Apple fixed
+	macOS but this patch guesses macOS 13.
+
 2023-02-18  Bruno Haible  <[email-masked]>
 
 	configmake: Add support for $build_os != $host_os.
diff --git a/doc/posix-functions/lseek.texi b/doc/posix-functions/lseek.texi
index 2f8e2b5877..3470524b12 100644
--- a/doc/posix-functions/lseek.texi
+++ b/doc/posix-functions/lseek.texi
@@ -37,4 +37,9 @@ IRIX 6.5.
 @item
 Some systems do not support @code{SEEK_DATA} and @code{SEEK_HOLE}:
 AIX, HP-UX, Microsoft Windows, NetBSD, OpenBSD.
+@item
+Some systems have a buggy @code{SEEK_DATA} and @code{SEEK_HOLE},
+and Gnulib works around the problem via @code{#undef SEEK_DATA}
+and @code{#undef SEEK_HOLE}:
+FreeBSD 13, macOS 12.
 @end itemize
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index bfc501e5a7..47b32eb445 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -40,6 +40,18 @@
 # undef _GL_INCLUDING_UNISTD_H
 #endif
 
+/* Avoid lseek bugs in FreeBSD, macOS <https://bugs.gnu.org/61386>.  */
+#if defined __FreeBSD__ && __FreeBSD__ < 14
+# undef SEEK_DATA
+# undef SEEK_HOLE
+#elif (defined __APPLE__ && defined __MACH__ \
+       && (!defined MAC_OS_X_VERSION_MIN_REQUIRED \
+           || MAC_OS_X_VERSION_MIN_REQUIRED < 130000))
+# include <sys/fcntl.h> /* It also defines the two macros.  */
+# undef SEEK_DATA
+# undef SEEK_HOLE
+#endif
+
 /* Get all possible declarations of gethostname().  */
 #if @GNULIB_GETHOSTNAME@ && @UNISTD_H_HAVE_WINSOCK2_H@ \
   && !defined _GL_INCLUDING_WINSOCK2_H
-- 
2.39.2