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 
28 ssize_t
support_copy_file_range(int infd,__off64_t * pinoff,int outfd,__off64_t * poutoff,size_t length,unsigned int flags)29 support_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 (infd, &instat) != 0 || fstat64 (outfd, &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 (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 (infd, buf, to_read);
82       else
83 	read_count = pread64 (infd, buf, to_read, *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 (outfd, p, end - p);
104 	  else
105 	    write_count = pwrite64 (outfd, p, end - p, *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 (infd, -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