This workaround is adapted from Coreutils.
* lib/copy-file-range.c [__linux__ && HAVE_COPY_FILE_RANGE]:
Include <sys/utsname.h>.
(copy_file_range): Use a stub to replace the copy_file_range of
Linux kernel versions 4.5 through 5.2.
* lib/unistd.in.h (copy_file_range):
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS):
* modules/copy-file-range (configure.ac):
* modules/unistd (unistd.h):
Support replacement of copy_file_range.
* m4/copy-file-range.m4 (gl_FUNC_COPY_FILE_RANGE):
Define HAVE_COPY_FILE_RANGE if the system has copy_file_range,
and on Linux check whether the system’s is known to work.
+2022-01-14 Paul Eggert <eggert@cs.ucla.edu>
+
+ copy-file-range: work around Linux kernel bug
+ This workaround is adapted from Coreutils.
+ * lib/copy-file-range.c [__linux__ && HAVE_COPY_FILE_RANGE]:
+ Include <sys/utsname.h>.
+ (copy_file_range): Use a stub to replace the copy_file_range of
+ Linux kernel versions 4.5 through 5.2.
+ * lib/unistd.in.h (copy_file_range):
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS):
+ * modules/copy-file-range (configure.ac):
+ * modules/unistd (unistd.h):
+ Support replacement of copy_file_range.
+ * m4/copy-file-range.m4 (gl_FUNC_COPY_FILE_RANGE):
+ Define HAVE_COPY_FILE_RANGE if the system has copy_file_range,
+ and on Linux check whether the system’s is known to work.
+
2022-01-14 Bruno Haible <bruno@clisp.org>
Avoid error "conditional LIBUNISTRING_COMPILE_... was never defined"
missing on many non-glibc platforms:
glibc 2.26, macOS 11.1, FreeBSD 12.0, NetBSD 9.0, OpenBSD 6.7, Minix 3.1.8, AIX 7.1, HP-UX 11.31, IRIX 6.5, Solaris 11.4, Cygwin 2.9, mingw, MSVC 14, Android 9.0.
But the replacement function is only a stub: It always fails with error ENOSYS.
+
+@item
+This function has many problems on Linux kernel versions before 5.3.
+On these kernel versions, the replacement function always fails with
+error ENOSYS.
@end itemize
Portability problems not fixed by Gnulib:
#include <errno.h>
+#if defined __linux__ && HAVE_COPY_FILE_RANGE
+# include <sys/utsname.h>
+#endif
+
ssize_t
copy_file_range (int infd, off_t *pinoff,
int outfd, off_t *poutoff,
size_t length, unsigned int flags)
{
+#undef copy_file_range
+
+#if defined __linux__ && HAVE_COPY_FILE_RANGE
+ /* The implementation of copy_file_range (which first appeared in
+ Linux kernel release 4.5) had many issues before release 5.3
+ <https://lwn.net/Articles/789527/>, so fail with ENOSYS for Linux
+ kernels 5.2 and earlier.
+
+ This workaround, and the configure-time check for Linux, can be
+ removed when such kernels (released March 2016 through September
+ 2019) are no longer a consideration. As of January 2021, the
+ furthest-future planned kernel EOL is December 2024 for kernel
+ release 4.19. */
+
+ static signed char ok;
+
+ if (! ok)
+ {
+ struct utsname name;
+ uname (&name);
+ char *p = name.release;
+ ok = ((p[1] != '.' || '5' < p[0]
+ || (p[0] == '5' && (p[3] != '.' || '2' < p[2])))
+ ? 1 : -1);
+ }
+
+ if (0 < ok)
+ return copy_file_range (infd, pinoff, outfd, poutoff, length, flags);
+#endif
+
/* There is little need to emulate copy_file_range with read+write,
since programs that use copy_file_range must fall back on
read+write anyway. */
#if @GNULIB_COPY_FILE_RANGE@
-# if !@HAVE_COPY_FILE_RANGE@
+# if @REPLACE_COPY_FILE_RANGE@
+# if !(defined __cplusplus && defined GNULIB_NAMESPACE)
+# undef copy_file_range
+# define copy_file_range rpl_copy_file_range
+# endif
+_GL_FUNCDECL_RPL (copy_file_range, ssize_t, (int ifd, off_t *ipos,
+ int ofd, off_t *opos,
+ size_t len, unsigned flags));
+_GL_CXXALIAS_RPL (copy_file_range, ssize_t, (int ifd, off_t *ipos,
+ int ofd, off_t *opos,
+ size_t len, unsigned flags));
+# else
+# if !@HAVE_COPY_FILE_RANGE@
_GL_FUNCDECL_SYS (copy_file_range, ssize_t, (int ifd, off_t *ipos,
int ofd, off_t *opos,
size_t len, unsigned flags));
+# endif
_GL_CXXALIAS_SYS (copy_file_range, ssize_t, (int ifd, off_t *ipos,
int ofd, off_t *opos,
size_t len, unsigned flags));
# endif
_GL_CXXALIASWARN (copy_file_range);
#elif defined GNULIB_POSIXCHECK
+# undef copy_file_range
# if HAVE_RAW_DECL_COPY_FILE_RANGE
_GL_WARN_ON_USE (copy_file_range,
"copy_file_range is unportable - "
AC_DEFUN([gl_FUNC_COPY_FILE_RANGE],
[
AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
+ AC_REQUIRE([AC_CANONICAL_HOST])
dnl Persuade glibc <unistd.h> to declare copy_file_range.
AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])
[AC_LANG_PROGRAM(
[[#include <unistd.h>
]],
- [[ssize_t (*func) (int, off_t *, int, off_t, size_t, unsigned)
+ [[ssize_t (*func) (int, off_t *, int, off_t *, size_t, unsigned)
= copy_file_range;
return func (0, 0, 0, 0, 0, 0) & 127;
]])
if test "$gl_cv_func_copy_file_range" != yes; then
HAVE_COPY_FILE_RANGE=0
+ else
+ AC_DEFINE([HAVE_COPY_FILE_RANGE], 1,
+ [Define to 1 if the function copy_file_range exists.])
+
+ case $host_os in
+ linux*)
+ AC_CACHE_CHECK([whether copy_file_range is known to work],
+ [gl_cv_copy_file_range_known_to_work],
+ [AC_COMPILE_IFELSE(
+ [AC_LANG_PROGRAM(
+ [[#include <linux/version.h>
+ ]],
+ [[#if LINUX_VERSION_CODE < KERNEL_VERSION (5, 3, 0)
+ #error "copy_file_range is buggy"
+ #endif
+ ]])],
+ [gl_cv_copy_file_range_known_to_work=yes],
+ [gl_cv_copy_file_range_known_to_work=no])])
+ if test "$gl_cv_copy_file_range_known_to_work" = no; then
+ REPLACE_COPY_FILE_RANGE=1
+ fi;;
+ esac
fi
])
REPLACE_ACCESS=0; AC_SUBST([REPLACE_ACCESS])
REPLACE_CHOWN=0; AC_SUBST([REPLACE_CHOWN])
REPLACE_CLOSE=0; AC_SUBST([REPLACE_CLOSE])
+ REPLACE_COPY_FILE_RANGE=0; AC_SUBST([REPLACE_COPY_FILE_RANGE])
REPLACE_DUP=0; AC_SUBST([REPLACE_DUP])
REPLACE_DUP2=0; AC_SUBST([REPLACE_DUP2])
REPLACE_EXECL=0; AC_SUBST([REPLACE_EXECL])
configure.ac:
gl_FUNC_COPY_FILE_RANGE
-gl_CONDITIONAL([GL_COND_OBJ_COPY_FILE_RANGE], [test $HAVE_COPY_FILE_RANGE = 0])
+gl_CONDITIONAL([GL_COND_OBJ_COPY_FILE_RANGE],
+ [test $HAVE_COPY_FILE_RANGE = 0 ||
+ test $REPLACE_COPY_FILE_RANGE = 1])
gl_UNISTD_MODULE_INDICATOR([copy-file-range])
Makefile.am:
sed -e 's|@''REPLACE_ACCESS''@|$(REPLACE_ACCESS)|g' \
-e 's|@''REPLACE_CHOWN''@|$(REPLACE_CHOWN)|g' \
-e 's|@''REPLACE_CLOSE''@|$(REPLACE_CLOSE)|g' \
+ -e 's|@''REPLACE_COPY_FILE_RANGE''@|$(REPLACE_COPY_FILE_RANGE)|g' \
-e 's|@''REPLACE_DUP''@|$(REPLACE_DUP)|g' \
-e 's|@''REPLACE_DUP2''@|$(REPLACE_DUP2)|g' \
-e 's|@''REPLACE_EXECL''@|$(REPLACE_EXECL)|g' \