bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] copy-file-range: work around Linux kernel bug


From: Paul Eggert
Subject: [PATCH] copy-file-range: work around Linux kernel bug
Date: Fri, 14 Jan 2022 17:33:01 -0800

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.
---
 ChangeLog                                | 17 ++++++++++++
 doc/glibc-functions/copy_file_range.texi |  5 ++++
 lib/copy-file-range.c                    | 34 ++++++++++++++++++++++++
 lib/unistd.in.h                          | 16 ++++++++++-
 m4/copy-file-range.m4                    | 25 ++++++++++++++++-
 m4/unistd_h.m4                           |  1 +
 modules/copy-file-range                  |  4 ++-
 modules/unistd                           |  1 +
 8 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 93fe05770b..a900fec78d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+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"
diff --git a/doc/glibc-functions/copy_file_range.texi 
b/doc/glibc-functions/copy_file_range.texi
index baac6e1302..f10271c157 100644
--- a/doc/glibc-functions/copy_file_range.texi
+++ b/doc/glibc-functions/copy_file_range.texi
@@ -24,6 +24,11 @@ This function exists only on Linux and FreeBSD and is 
therefore
 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:
diff --git a/lib/copy-file-range.c b/lib/copy-file-range.c
index 96f1ec7c5e..1ec7f4de67 100644
--- a/lib/copy-file-range.c
+++ b/lib/copy-file-range.c
@@ -20,11 +20,45 @@
 
 #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.  */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 3386f0b0f7..57df09ecdf 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -415,16 +415,30 @@ _GL_CXXALIASWARN (close);
 
 
 #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 - "
diff --git a/m4/copy-file-range.m4 b/m4/copy-file-range.m4
index 4c7ec4eaaf..1b8b9d8858 100644
--- a/m4/copy-file-range.m4
+++ b/m4/copy-file-range.m4
@@ -7,6 +7,7 @@ dnl with or without modifications, as long as this notice is 
preserved.
 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])
@@ -21,7 +22,7 @@ AC_DEFUN([gl_FUNC_COPY_FILE_RANGE],
        [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;
           ]])
@@ -32,5 +33,27 @@ AC_DEFUN([gl_FUNC_COPY_FILE_RANGE],
 
   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
 ])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index f93f97a1bd..4c66ccc0a4 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -222,6 +222,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
   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])
diff --git a/modules/copy-file-range b/modules/copy-file-range
index e2a4ffef46..4707f97561 100644
--- a/modules/copy-file-range
+++ b/modules/copy-file-range
@@ -11,7 +11,9 @@ unistd
 
 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:
diff --git a/modules/unistd b/modules/unistd
index 326bae0e48..01da57e23a 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -176,6 +176,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status 
$(CXXDEFS_H) $(ARG_NONNULL_H
          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' \
-- 
2.32.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]