From ba5e6885d2c255648cddb87b4e795659c1990374 Mon Sep 17 00:00:00 2001 From: =?utf8?q?P=C3=A1draig=20Brady?= Date: Wed, 12 May 2021 23:47:38 +0100 Subject: [PATCH] copy: disallow copy_file_range() on Linux kernels before 5.3 copy_file_range() before Linux kernel release 5.3 had many issues, as described at https://lwn.net/Articles/789527/, which was referenced from https://lwn.net/Articles/846403/; a more general article discussing the generality of copy_file_range(). Linux kernel 5.3 was released in September 2019, which is new enough that we need to actively avoid older kernels. * src/copy.c (functional_copy_file_range): A new function that returns false for Linux kernels before version 5.3. (sparse_copy): Call this new function to gate use of copy_file_range(). --- src/copy.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/copy.c b/src/copy.c index e929963901..ef59bb82c5 100644 --- a/src/copy.c +++ b/src/copy.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #if HAVE_HURD_H @@ -64,6 +65,7 @@ #include "write-any-file.h" #include "areadlink.h" #include "yesno.h" +#include "xstrtol.h" #include "selinux.h" #if USE_XATTR @@ -244,6 +246,47 @@ create_hole (int fd, char const *name, bool punch_holes, off_t size) return true; } +/* copy_file_range() before Linux kernel release 5.3 had many issues, + as described at https://lwn.net/Articles/789527/, + so return FALSE for Linux kernels earlier than that. + This function can be removed when such kernels (released before Sep 2019) + are no longer a consideration. */ + +static bool +functional_copy_file_range (void) +{ +#ifdef __linux__ + static int version_allowed = -1; + + if (version_allowed == -1) + version_allowed = 0; + else + return version_allowed; + + struct utsname name; + if (uname (&name) == -1) + return version_allowed; + + char *p = name.release; + uintmax_t ver[2] = {0, 0}; + size_t iver = 0; + + do + { + strtol_error err = xstrtoumax (p, &p, 10, &ver[iver], NULL); + if (err != LONGINT_OK || *p++ != '.') + break; + } + while (++iver < ARRAY_CARDINALITY (ver)); + + version_allowed = (ver[0] > 5 || (ver[0] == 5 && ver[1] >= 3)); + + return version_allowed; +#else + return true; +#endif + +} /* Copy the regular file open on SRC_FD/SRC_NAME to DST_FD/DST_NAME, honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer @@ -266,9 +309,9 @@ sparse_copy (int src_fd, int dest_fd, char *buf, size_t buf_size, *last_write_made_hole = false; *total_n_read = 0; - /* If not looking for holes, use copy_file_range if available, + /* If not looking for holes, use copy_file_range if functional, but don't use if reflink disallowed as that may be implicit. */ - if ((! hole_size) && allow_reflink) + if ((! hole_size) && allow_reflink && functional_copy_file_range ()) while (max_n_read) { /* Copy at most COPY_MAX bytes at a time; this is min -- 2.47.2