1 /****************************************************************************
2 * fs/vfs/fs_sendfile.c
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership. The
7 * ASF licenses this file to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance with the
9 * License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16 * License for the specific language governing permissions and limitations
17 * under the License.
18 *
19 ****************************************************************************/
20
21 /****************************************************************************
22 * Included Files
23 ****************************************************************************/
24
25 #include "vfs_config.h"
26
27 #include <fs/file.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <errno.h>
32
33
34 #ifndef CONFIG_LIB_SENDFILE_BUFSIZE
35 # define CONFIG_LIB_SENDFILE_BUFSIZE 512
36 #endif
37
38 /****************************************************************************
39 * Public Functions
40 ****************************************************************************/
41
42 /****************************************************************************
43 * Name: sendfile
44 *
45 * Description:
46 * sendfile() copies data between one file descriptor and another.
47 * Used with file descriptors it basically just wraps a sequence of
48 * reads() and writes() to perform a copy.
49 *
50 * If the destination descriptor is a socket, it gives a better
51 * performance than simple reds() and writes(). The data is read directly
52 * into the net buffer and the whole tcp window is filled if possible.
53 *
54 * NOTE: This interface is *not* specified in POSIX.1-2001, or other
55 * standards. The implementation here is very similar to the Linux
56 * sendfile interface. Other UNIX systems implement sendfile() with
57 * different semantics and prototypes. sendfile() should not be used
58 * in portable programs.
59 *
60 * Input Parameters:
61 * infd - A file (or socket) descriptor opened for reading
62 * outfd - A descriptor opened for writing.
63 * offset - If 'offset' is not NULL, then it points to a variable
64 * holding the file offset from which sendfile() will start
65 * reading data from 'infd'. When sendfile() returns, this
66 * variable will be set to the offset of the byte following
67 * the last byte that was read. If 'offset' is not NULL,
68 * then sendfile() does not modify the current file offset of
69 * 'infd'; otherwise the current file offset is adjusted to
70 * reflect the number of bytes read from 'infd.'
71 *
72 * If 'offset' is NULL, then data will be read from 'infd'
73 * starting at the current file offset, and the file offset
74 * will be updated by the call.
75 * count - The number of bytes to copy between the file descriptors.
76 *
77 * Returned Value:
78 * If the transfer was successful, the number of bytes written to outfd is
79 * returned. On error, -1 is returned, and errno is set appropriately.
80 * There error values are those returned by read() or write() plus:
81 *
82 * EINVAL - Bad input parameters.
83 * ENOMEM - Could not allocated an I/O buffer
84 *
85 ****************************************************************************/
86
sendfile(int outfd,int infd,off_t * offset,size_t count)87 ssize_t sendfile(int outfd, int infd, off_t *offset, size_t count)
88 {
89 uint8_t *iobuffer;
90 uint8_t *wrbuffer;
91 off_t startpos = 0;
92 ssize_t nbytesread;
93 ssize_t nbyteswritten;
94 size_t ntransferred;
95 bool endxfr;
96
97 /* Get the current file position. */
98
99 if (offset)
100 {
101 /* Use lseek to get the current file position */
102
103 startpos = lseek(infd, 0, SEEK_CUR);
104 if (startpos == (off_t)-1)
105 {
106 return VFS_ERROR;
107 }
108
109 /* Use lseek again to set the new file position */
110
111 if (lseek(infd, *offset, SEEK_SET) == (off_t)-1)
112 {
113 return VFS_ERROR;
114 }
115 }
116
117 /* Allocate an I/O buffer */
118
119 iobuffer = (void *)malloc(CONFIG_LIB_SENDFILE_BUFSIZE);
120 if (!iobuffer)
121 {
122 set_errno(ENOMEM);
123 return VFS_ERROR;
124 }
125
126 /* Now transfer 'count' bytes from the infd to the outfd */
127
128 for (ntransferred = 0, endxfr = false; ntransferred < count && !endxfr; )
129 {
130 /* Loop until the read side of the transfer comes to some conclusion */
131
132 do
133 {
134 /* Read a buffer of data from the infd */
135
136 nbytesread = read(infd, iobuffer, CONFIG_LIB_SENDFILE_BUFSIZE);
137
138 /* Check for end of file */
139
140 if (nbytesread == 0)
141 {
142 /* End of file. Break out and return current number of bytes
143 * transferred.
144 */
145
146 endxfr = true;
147 break;
148 }
149
150 /* Check for a read ERROR. EINTR is a special case. This function
151 * should break out and return an error if EINTR is returned and
152 * no data has been transferred. But what should it do if some
153 * data has been transferred? I suppose just continue?
154 */
155
156 else if (nbytesread < 0)
157 {
158 int errcode = get_errno();
159
160 /* EINTR is not an error (but will still stop the copy) */
161
162 if (errcode != EINTR || ntransferred == 0)
163 {
164 /* Read error. Break out and return the error condition. */
165
166 set_errno(errcode);
167 ntransferred = VFS_ERROR;
168 endxfr = true;
169 break;
170 }
171 }
172 }
173 while (nbytesread < 0);
174
175 /* Was anything read? */
176
177 if (!endxfr)
178 {
179 /* Yes.. Loop until the read side of the transfer comes to some
180 * conclusion.
181 */
182
183 wrbuffer = iobuffer;
184 do
185 {
186 /* Write the buffer of data to the outfd */
187
188 nbyteswritten = write(outfd, wrbuffer, nbytesread);
189
190 /* Check for a complete (or parial) write. write() should not
191 * return zero.
192 */
193
194 if (nbyteswritten >= 0)
195 {
196 /* Advance the buffer pointer and decrement the number of bytes
197 * remaining in the iobuffer. Typically, nbytesread will now
198 * be zero.
199 */
200
201 wrbuffer += nbyteswritten;
202 nbytesread -= nbyteswritten;
203
204 /* Increment the total number of bytes successfully transferred. */
205
206 ntransferred += nbyteswritten;
207 }
208
209 /* Otherwise an error occurred */
210
211 else
212 {
213 int errcode = get_errno();
214
215 /* Check for a read ERROR. EINTR is a special case. This
216 * function should break out and return an error if EINTR
217 * is returned and no data has been transferred. But what
218 * should it do if some data has been transferred? I
219 * suppose just continue?
220 */
221
222 if (errcode != EINTR || ntransferred == 0)
223 {
224 /* Write error. Break out and return the error
225 * condition.
226 */
227
228 set_errno(errcode);
229 ntransferred = VFS_ERROR;
230 endxfr = true;
231 break;
232 }
233 }
234 }
235 while (nbytesread > 0);
236 }
237 }
238
239 /* Release the I/O buffer */
240
241 free(iobuffer);
242
243 /* Return the current file position */
244
245 if (offset)
246 {
247 /* Use lseek to get the current file position */
248
249 off_t curpos = lseek(infd, 0, SEEK_CUR);
250 if (curpos == (off_t)-1)
251 {
252 return VFS_ERROR;
253 }
254
255 /* Return the current file position */
256
257 *offset = curpos;
258
259 /* Use lseek again to restore the original file position */
260
261 if (lseek(infd, startpos, SEEK_SET) == (off_t)-1)
262 {
263 return VFS_ERROR;
264 }
265 }
266
267 /* Finally return the number of bytes actually transferred (or VFS_ERROR
268 * if any failure occurred).
269 */
270
271 return ntransferred;
272 }
273
274