diff options
author | H.J. Lu <hjl.tools@gmail.com> | 2018-08-31 13:07:47 -0700 |
---|---|---|
committer | H.J. Lu <hjl.tools@gmail.com> | 2018-08-31 13:08:02 -0700 |
commit | 81b9d87bae23efc42e2121ac066fe38fceb96124 (patch) | |
tree | 5d7c20057b29cea4cfa895f5301d6133e1fa6bc5 /support/support_copy_file_range.c | |
parent | Fix test failure with -DNDEBUG. (diff) | |
download | glibc-81b9d87bae23efc42e2121ac066fe38fceb96124.tar.gz glibc-81b9d87bae23efc42e2121ac066fe38fceb96124.tar.bz2 glibc-81b9d87bae23efc42e2121ac066fe38fceb96124.zip |
test-container: Use xcopy_file_range for cross-device copy [BZ #23597]
copy_file_range can't be used to copy a file from glibc source directory
to glibc build directory since they may be on different filesystems.
This patch adds xcopy_file_range for cross-device copy.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
[BZ #23597]
* support/Makefile (libsupport-routines): Add
support_copy_file_range and xcopy_file_range.
* support/support.h: Include <sys/types.h>.
(support_copy_file_range): New prototype.
* support/support_copy_file_range.c: New file. Copied and
modified from io/copy_file_range-compat.c.
* support/test-container.c (copy_one_file): Call xcopy_file_rang
instead of copy_file_range.
* support/xcopy_file_range.c: New file.
* support/xunistd.h (xcopy_file_range): New prototype.
Diffstat (limited to 'support/support_copy_file_range.c')
-rw-r--r-- | support/support_copy_file_range.c | 143 |
1 files changed, 143 insertions, 0 deletions
diff --git a/support/support_copy_file_range.c b/support/support_copy_file_range.c new file mode 100644 index 0000000000..9a1e39773e --- /dev/null +++ b/support/support_copy_file_range.c @@ -0,0 +1,143 @@ +/* Simplified copy_file_range with cross-device copy. + Copyright (C) 2018 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <support/support.h> + +ssize_t +support_copy_file_range (int infd, __off64_t *pinoff, + int outfd, __off64_t *poutoff, + size_t length, unsigned int flags) +{ + if (flags != 0) + { + errno = EINVAL; + return -1; + } + + struct stat64 instat; + struct stat64 outstat; + if (fstat64 (infd, &instat) != 0 || fstat64 (outfd, &outstat) != 0) + return -1; + if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode)) + { + 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. */ + errno = EINVAL; + return -1; + } + + /* The output descriptor must not have O_APPEND set. */ + if (fcntl (outfd, F_GETFL) & O_APPEND) + { + 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 = pread64 (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 = pwrite64 (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) lseek64 (infd, -overread, SEEK_CUR); + 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; +} |