From 816d84f1380dfbb92baceb6c8facab2ac3ae17ea Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Thu, 27 Jan 2022 13:00:41 -0800 Subject: [PATCH] cat: prefer copy_file_range to read+write * src/cat.c (copy_cat): New function. (main): Use it. --- NEWS | 3 +++ src/cat.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index b453f01ad2..561087ccc0 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,9 @@ GNU coreutils NEWS -*- outline -*- ** Changes in behavior + cat now uses the copy_file_range syscall if available, when doing + simple copies between regular files. + date +'%-N' now suppresses excess trailing digits, instead of always padding them with zeros to 9 digits. It uses clock_getres and clock_gettime to infer the clock resolution. diff --git a/src/cat.c b/src/cat.c index 1d6f7fbff9..e9535240e6 100644 --- a/src/cat.c +++ b/src/cat.c @@ -499,6 +499,42 @@ cat (char *inbuf, idx_t insize, char *outbuf, idx_t outsize, } } +/* Copy data from input to output using copy_file_range if possible. + Return 1 if successful, 0 if ordinary read+write should be tried, + -1 if a serious problem has been diagnosed. */ + +static int +copy_cat (void) +{ + /* Copy at most COPY_MAX bytes at a time; this is min + (SSIZE_MAX, SIZE_MAX) truncated to a value that is + surely aligned well. */ + ssize_t copy_max = MIN (SSIZE_MAX, SIZE_MAX) >> 30 << 30; + + /* copy_file_range does not support some cases, and it + incorrectly returns 0 when reading from the proc file + system on the Linux kernel through at least 5.6.19 (2020), + so fall back on read+write if the copy_file_range is + unsupported or the input file seems empty. */ + + for (bool some_copied = false; ; some_copied = true) + switch (copy_file_range (input_desc, NULL, STDOUT_FILENO, NULL, + copy_max, 0)) + { + case 0: + return some_copied; + + case -1: + if (errno == ENOSYS || is_ENOTSUP (errno) || errno == EINVAL + || errno == EBADF || errno == EXDEV || errno == ETXTBSY + || errno == EPERM) + return 0; + error (0, errno, "%s", quotef (infile)); + return -1; + } +} + + int main (int argc, char **argv) { @@ -685,15 +721,25 @@ main (int argc, char **argv) char *inbuf; /* Select which version of 'cat' to use. If any format-oriented - options were given use 'cat'; otherwise use 'simple_cat'. */ + options were given use 'cat'; if not, use 'copy_cat' if it + works, 'simple_cat' otherwise. */ if (! (number || show_ends || show_nonprinting || show_tabs || squeeze_blank)) { - insize = MAX (insize, outsize); - inbuf = xalignalloc (page_size, insize); - - ok &= simple_cat (inbuf, insize); + int copy_cat_status = + out_isreg && S_ISREG (stat_buf.st_mode) ? copy_cat () : 0; + if (copy_cat_status != 0) + { + inbuf = NULL; + ok &= 0 < copy_cat_status; + } + else + { + insize = MAX (insize, outsize); + inbuf = xalignalloc (page_size, insize); + ok &= simple_cat (inbuf, insize); + } } else { -- 2.47.2