1/* Simplified copy_file_range with cross-device copy.
2 Copyright (C) 2018-2022 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with the GNU C Library; if not, see
17 <https://www.gnu.org/licenses/>. */
18
19#include <errno.h>
20#include <fcntl.h>
21#include <inttypes.h>
22#include <limits.h>
23#include <sys/stat.h>
24#include <sys/types.h>
25#include <unistd.h>
26#include <support/support.h>
27
28ssize_t
29support_copy_file_range (int infd, __off64_t *pinoff,
30 int outfd, __off64_t *poutoff,
31 size_t length, unsigned int flags)
32{
33 if (flags != 0)
34 {
35 errno = EINVAL;
36 return -1;
37 }
38
39 struct stat64 instat;
40 struct stat64 outstat;
41 if (fstat64 (fd: infd, buf: &instat) != 0 || fstat64 (fd: outfd, buf: &outstat) != 0)
42 return -1;
43 if (S_ISDIR (instat.st_mode) || S_ISDIR (outstat.st_mode))
44 {
45 errno = EISDIR;
46 return -1;
47 }
48 if (!S_ISREG (instat.st_mode) || !S_ISREG (outstat.st_mode))
49 {
50 /* We need a regular input file so that the we can seek
51 backwards in case of a write failure. */
52 errno = EINVAL;
53 return -1;
54 }
55
56 /* The output descriptor must not have O_APPEND set. */
57 if (fcntl (fd: outfd, F_GETFL) & O_APPEND)
58 {
59 errno = EBADF;
60 return -1;
61 }
62
63 /* Avoid an overflow in the result. */
64 if (length > SSIZE_MAX)
65 length = SSIZE_MAX;
66
67 /* Main copying loop. The buffer size is arbitrary and is a
68 trade-off between stack size consumption, cache usage, and
69 amortization of system call overhead. */
70 size_t copied = 0;
71 char buf[8192];
72 while (length > 0)
73 {
74 size_t to_read = length;
75 if (to_read > sizeof (buf))
76 to_read = sizeof (buf);
77
78 /* Fill the buffer. */
79 ssize_t read_count;
80 if (pinoff == NULL)
81 read_count = read (fd: infd, buf: buf, nbytes: to_read);
82 else
83 read_count = pread64 (fd: infd, buf: buf, nbytes: to_read, offset: *pinoff);
84 if (read_count == 0)
85 /* End of file reached prematurely. */
86 return copied;
87 if (read_count < 0)
88 {
89 if (copied > 0)
90 /* Report the number of bytes copied so far. */
91 return copied;
92 return -1;
93 }
94 if (pinoff != NULL)
95 *pinoff += read_count;
96
97 /* Write the buffer part which was read to the destination. */
98 char *end = buf + read_count;
99 for (char *p = buf; p < end; )
100 {
101 ssize_t write_count;
102 if (poutoff == NULL)
103 write_count = write (fd: outfd, buf: p, n: end - p);
104 else
105 write_count = pwrite64 (fd: outfd, buf: p, n: end - p, offset: *poutoff);
106 if (write_count < 0)
107 {
108 /* Adjust the input read position to match what we have
109 written, so that the caller can pick up after the
110 error. */
111 size_t written = p - buf;
112 /* NB: This needs to be signed so that we can form the
113 negative value below. */
114 ssize_t overread = read_count - written;
115 if (pinoff == NULL)
116 {
117 if (overread > 0)
118 {
119 /* We are on an error recovery path, so we
120 cannot deal with failure here. */
121 int save_errno = errno;
122 (void) lseek64 (fd: infd, offset: -overread, SEEK_CUR);
123 errno = save_errno;
124 }
125 }
126 else /* pinoff != NULL */
127 *pinoff -= overread;
128
129 if (copied + written > 0)
130 /* Report the number of bytes copied so far. */
131 return copied + written;
132 return -1;
133 }
134 p += write_count;
135 if (poutoff != NULL)
136 *poutoff += write_count;
137 } /* Write loop. */
138
139 copied += read_count;
140 length -= read_count;
141 }
142 return copied;
143}
144

source code of glibc/support/support_copy_file_range.c