* MODULES.html.sh: Add copy-file-range. * lib/copy-file-range.c, m4/copy-file-range.m4: * modules/copy-file-range: New files. * lib/unistd.in.h (copy_file_range): Declare. * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Set up GNULIB_COPY_FILE_RANGE and HAVE_COPY_FILE_RANGE. * modules/unistd (unistd.h): Substitute them. --- ChangeLog | 11 +++ MODULES.html.sh | 1 + lib/copy-file-range.c | 165 ++++++++++++++++++++++++++++++++++++++++ lib/unistd.in.h | 18 +++++ m4/copy-file-range.m4 | 18 +++++ m4/unistd_h.m4 | 4 +- modules/copy-file-range | 33 ++++++++ modules/unistd | 2 + 8 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 lib/copy-file-range.c create mode 100644 m4/copy-file-range.m4 create mode 100644 modules/copy-file-range
diff --git a/ChangeLog b/ChangeLog index 5e1e69ce3..9ff35e9c1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2019-06-04 Paul Eggert <egg...@cs.ucla.edu> + + copy-file-range: new module + * MODULES.html.sh: Add copy-file-range. + * lib/copy-file-range.c, m4/copy-file-range.m4: + * modules/copy-file-range: New files. + * lib/unistd.in.h (copy_file_range): Declare. + * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): + Set up GNULIB_COPY_FILE_RANGE and HAVE_COPY_FILE_RANGE. + * modules/unistd (unistd.h): Substitute them. + 2019-05-28 Bruno Haible <br...@clisp.org> binary-io: Attempted use of O_BINARY on consoles no longer fails. diff --git a/MODULES.html.sh b/MODULES.html.sh index 78b6d4918..80f0c2973 100755 --- a/MODULES.html.sh +++ b/MODULES.html.sh @@ -2735,6 +2735,7 @@ func_all_modules () func_begin_table func_module binary-io + func_module copy-file-range func_module dup3 func_module fcntl-safer func_module fd-safer-flag diff --git a/lib/copy-file-range.c b/lib/copy-file-range.c new file mode 100644 index 000000000..a1448d0d9 --- /dev/null +++ b/lib/copy-file-range.c @@ -0,0 +1,165 @@ +/* Emulation of copy_file_range. + Copyright 2017-2019 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* This file is adapted from glibc io/copy_file_range-compat.c, with a + small number of changes to port to non-glibc platforms. */ + +#include <libc-config.h> + +/* The following macros should be defined: + + COPY_FILE_RANGE_DECL Declaration specifiers for the function below. + COPY_FILE_RANGE Name of the function to define. */ + +#define COPY_FILE_RANGE_DECL +#define COPY_FILE_RANGE copy_file_range + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +COPY_FILE_RANGE_DECL +ssize_t +COPY_FILE_RANGE (int infd, off_t *pinoff, + int outfd, off_t *poutoff, + size_t length, unsigned int flags) +{ + if (flags != 0) + { + __set_errno (EINVAL); + return -1; + } + + { + struct stat instat; + struct stat outstat; + if (fstat (infd, &instat) != 0 || fstat (outfd, &outstat) != 0) + return -1; + if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode)) + { + __set_errno (EISDIR); + return -1; + } + if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode)) + { + /* We need a regular input file so that the we can seek + backwards in case of a write failure. */ + __set_errno (EINVAL); + return -1; + } + if (instat.st_dev != outstat.st_dev) + { + /* Cross-device copies are not supported. */ + __set_errno (EXDEV); + return -1; + } + } + + /* The output descriptor must not have O_APPEND set. */ + { + int flags = fcntl (outfd, F_GETFL); + if (flags & O_APPEND) + { + __set_errno (EBADF); + return -1; + } + } + + /* Avoid an overflow in the result. */ + if (length > SSIZE_MAX) + length = SSIZE_MAX; + + /* Main copying loop. The buffer size is arbitrary and is a + trade-off between stack size consumption, cache usage, and + amortization of system call overhead. */ + size_t copied = 0; + char buf[8192]; + while (length > 0) + { + size_t to_read = length; + if (to_read > sizeof (buf)) + to_read = sizeof (buf); + + /* Fill the buffer. */ + ssize_t read_count; + if (pinoff == NULL) + read_count = read (infd, buf, to_read); + else + read_count = pread (infd, buf, to_read, *pinoff); + if (read_count == 0) + /* End of file reached prematurely. */ + return copied; + if (read_count < 0) + { + if (copied > 0) + /* Report the number of bytes copied so far. */ + return copied; + return -1; + } + if (pinoff != NULL) + *pinoff += read_count; + + /* Write the buffer part which was read to the destination. */ + char *end = buf + read_count; + for (char *p = buf; p < end; ) + { + ssize_t write_count; + if (poutoff == NULL) + write_count = write (outfd, p, end - p); + else + write_count = pwrite (outfd, p, end - p, *poutoff); + if (write_count < 0) + { + /* Adjust the input read position to match what we have + written, so that the caller can pick up after the + error. */ + size_t written = p - buf; + /* NB: This needs to be signed so that we can form the + negative value below. */ + ssize_t overread = read_count - written; + if (pinoff == NULL) + { + if (overread > 0) + { + /* We are on an error recovery path, so we + cannot deal with failure here. */ + int save_errno = errno; + (void) lseek (infd, -overread, SEEK_CUR); + __set_errno (save_errno); + } + } + else /* pinoff != NULL */ + *pinoff -= overread; + + if (copied + written > 0) + /* Report the number of bytes copied so far. */ + return copied + written; + return -1; + } + p += write_count; + if (poutoff != NULL) + *poutoff += write_count; + } /* Write loop. */ + + copied += read_count; + length -= read_count; + } + return copied; +} diff --git a/lib/unistd.in.h b/lib/unistd.in.h index 0ff7396b3..9ffb2e990 100644 --- a/lib/unistd.in.h +++ b/lib/unistd.in.h @@ -331,6 +331,24 @@ _GL_WARN_ON_USE (close, "close does not portably work on sockets - " #endif +#if @GNULIB_COPY_FILE_RANGE@ +# 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)); +_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 +/* Assume copy_file_range is always declared. */ +_GL_WARN_ON_USE (copy_file_range, + "copy_file_range is unportable - " + "use gnulib module copy_file_range for portability"); +#endif + + #if @GNULIB_DUP@ # if @REPLACE_DUP@ # if !(defined __cplusplus && defined GNULIB_NAMESPACE) diff --git a/m4/copy-file-range.m4 b/m4/copy-file-range.m4 new file mode 100644 index 000000000..edd2c4aed --- /dev/null +++ b/m4/copy-file-range.m4 @@ -0,0 +1,18 @@ +# copy-file-range.m4 +dnl Copyright 2019 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +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]) + + dnl Persuade glibc <unistd.h> to declare copy_file_range. + AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS]) + + AC_CHECK_FUNCS_ONCE([copy_file_range]) + if test $ac_cv_func_copy_file_range != yes; then + HAVE_COPY_FILE_RANGE=0 + fi +]) diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4 index a04055d2a..a3b3905f8 100644 --- a/m4/unistd_h.m4 +++ b/m4/unistd_h.m4 @@ -1,4 +1,4 @@ -# unistd_h.m4 serial 74 +# unistd_h.m4 serial 75 dnl Copyright (C) 2006-2019 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -64,6 +64,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], GNULIB_CHDIR=0; AC_SUBST([GNULIB_CHDIR]) GNULIB_CHOWN=0; AC_SUBST([GNULIB_CHOWN]) GNULIB_CLOSE=0; AC_SUBST([GNULIB_CLOSE]) + GNULIB_COPY_FILE_RANGE=0; AC_SUBST([GNULIB_COPY_FILE_RANGE]) GNULIB_DUP=0; AC_SUBST([GNULIB_DUP]) GNULIB_DUP2=0; AC_SUBST([GNULIB_DUP2]) GNULIB_DUP3=0; AC_SUBST([GNULIB_DUP3]) @@ -113,6 +114,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS], GNULIB_WRITE=0; AC_SUBST([GNULIB_WRITE]) dnl Assume proper GNU behavior unless another module says otherwise. HAVE_CHOWN=1; AC_SUBST([HAVE_CHOWN]) + HAVE_COPY_FILE_RANGE=1; AC_SUBST([HAVE_COPY_FILE_RANGE]) HAVE_DUP2=1; AC_SUBST([HAVE_DUP2]) HAVE_DUP3=1; AC_SUBST([HAVE_DUP3]) HAVE_EUIDACCESS=1; AC_SUBST([HAVE_EUIDACCESS]) diff --git a/modules/copy-file-range b/modules/copy-file-range new file mode 100644 index 000000000..18742ef3b --- /dev/null +++ b/modules/copy-file-range @@ -0,0 +1,33 @@ +Description: +Copy parts of files + +Files: +lib/copy-file-range.c +m4/copy-file-range.m4 + +Depends-on: +c99 +inttypes-incomplete [test $HAVE_COPY_FILE_RANGE = 0] +largefile +libc-config [test $HAVE_COPY_FILE_RANGE = 0] +pread [test $HAVE_COPY_FILE_RANGE = 0] +pwrite [test $HAVE_COPY_FILE_RANGE = 0] +unistd + +configure.ac: +gl_FUNC_COPY_FILE_RANGE +if test $HAVE_COPY_FILE_RANGE = 0; then + AC_LIBOBJ([copy-file-range]) +fi +gl_UNISTD_MODULE_INDICATOR([copy-file-range]) + +Makefile.am: + +Include: +<unistd.h> + +License: +LGPLv2+ + +Maintainer: +all diff --git a/modules/unistd b/modules/unistd index fc1a27fe1..e29c1ad94 100644 --- a/modules/unistd +++ b/modules/unistd @@ -39,6 +39,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's/@''GNULIB_CHDIR''@/$(GNULIB_CHDIR)/g' \ -e 's/@''GNULIB_CHOWN''@/$(GNULIB_CHOWN)/g' \ -e 's/@''GNULIB_CLOSE''@/$(GNULIB_CLOSE)/g' \ + -e 's/@''GNULIB_COPY_FILE_RANGE''@/$(GNULIB_COPY_FILE_RANGE)/g' \ -e 's/@''GNULIB_DUP''@/$(GNULIB_DUP)/g' \ -e 's/@''GNULIB_DUP2''@/$(GNULIB_DUP2)/g' \ -e 's/@''GNULIB_DUP3''@/$(GNULIB_DUP3)/g' \ @@ -89,6 +90,7 @@ unistd.h: unistd.in.h $(top_builddir)/config.status $(CXXDEFS_H) $(ARG_NONNULL_H -e 's/@''GNULIB_WRITE''@/$(GNULIB_WRITE)/g' \ < $(srcdir)/unistd.in.h | \ sed -e 's|@''HAVE_CHOWN''@|$(HAVE_CHOWN)|g' \ + -e 's|@''HAVE_COPY_FILE_RANGE''@|$(HAVE_COPY_FILE_RANGE)|g' \ -e 's|@''HAVE_DUP2''@|$(HAVE_DUP2)|g' \ -e 's|@''HAVE_DUP3''@|$(HAVE_DUP3)|g' \ -e 's|@''HAVE_EUIDACCESS''@|$(HAVE_EUIDACCESS)|g' \ -- 2.17.1