1 /****************************************************************************
2 * fs/vfs/fs_sendfile.c
3 *
4 * Copyright (C) 2007, 2009, 2011, 2013, 2017-2018 Gregory Nutt. All
5 * rights reserved.
6 * Author: Gregory Nutt <gnutt@nuttx.org>
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 * 3. Neither the name NuttX nor the names of its contributors may be
19 * used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
29 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 *
35 ****************************************************************************/
36
37 /****************************************************************************
38 * Included Files
39 ****************************************************************************/
40
41 #include "vfs_config.h"
42
43 #include <fs/file.h>
44 #include <stdbool.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 #include <errno.h>
48
49
50 #ifndef CONFIG_LIB_SENDFILE_BUFSIZE
51 # define CONFIG_LIB_SENDFILE_BUFSIZE 512
52 #endif
53
54 /****************************************************************************
55 * Public Functions
56 ****************************************************************************/
57
58 /****************************************************************************
59 * Name: sendfile
60 *
61 * Description:
62 * sendfile() copies data between one file descriptor and another.
63 * Used with file descriptors it basically just wraps a sequence of
64 * reads() and writes() to perform a copy.
65 *
66 * If the destination descriptor is a socket, it gives a better
67 * performance than simple reds() and writes(). The data is read directly
68 * into the net buffer and the whole tcp window is filled if possible.
69 *
70 * NOTE: This interface is *not* specified in POSIX.1-2001, or other
71 * standards. The implementation here is very similar to the Linux
72 * sendfile interface. Other UNIX systems implement sendfile() with
73 * different semantics and prototypes. sendfile() should not be used
74 * in portable programs.
75 *
76 * Input Parameters:
77 * infd - A file (or socket) descriptor opened for reading
78 * outfd - A descriptor opened for writing.
79 * offset - If 'offset' is not NULL, then it points to a variable
80 * holding the file offset from which sendfile() will start
81 * reading data from 'infd'. When sendfile() returns, this
82 * variable will be set to the offset of the byte following
83 * the last byte that was read. If 'offset' is not NULL,
84 * then sendfile() does not modify the current file offset of
85 * 'infd'; otherwise the current file offset is adjusted to
86 * reflect the number of bytes read from 'infd.'
87 *
88 * If 'offset' is NULL, then data will be read from 'infd'
89 * starting at the current file offset, and the file offset
90 * will be updated by the call.
91 * count - The number of bytes to copy between the file descriptors.
92 *
93 * Returned Value:
94 * If the transfer was successful, the number of bytes written to outfd is
95 * returned. On error, -1 is returned, and errno is set appropriately.
96 * There error values are those returned by read() or write() plus:
97 *
98 * EINVAL - Bad input parameters.
99 * ENOMEM - Could not allocated an I/O buffer
100 *
101 ****************************************************************************/
102
sendfile(int outfd,int infd,off_t * offset,size_t count)103 ssize_t sendfile(int outfd, int infd, off_t *offset, size_t count)
104 {
105 uint8_t *iobuffer;
106 uint8_t *wrbuffer;
107 off_t startpos = 0;
108 ssize_t nbytesread;
109 ssize_t nbyteswritten;
110 size_t ntransferred;
111 bool endxfr;
112
113 /* Get the current file position. */
114
115 if (offset)
116 {
117 /* Use lseek to get the current file position */
118
119 startpos = lseek(infd, 0, SEEK_CUR);
120 if (startpos == (off_t)-1)
121 {
122 return VFS_ERROR;
123 }
124
125 /* Use lseek again to set the new file position */
126
127 if (lseek(infd, *offset, SEEK_SET) == (off_t)-1)
128 {
129 return VFS_ERROR;
130 }
131 }
132
133 /* Allocate an I/O buffer */
134
135 iobuffer = (void *)malloc(CONFIG_LIB_SENDFILE_BUFSIZE);
136 if (!iobuffer)
137 {
138 set_errno(ENOMEM);
139 return VFS_ERROR;
140 }
141
142 /* Now transfer 'count' bytes from the infd to the outfd */
143
144 for (ntransferred = 0, endxfr = false; ntransferred < count && !endxfr; )
145 {
146 /* Loop until the read side of the transfer comes to some conclusion */
147
148 do
149 {
150 /* Read a buffer of data from the infd */
151
152 nbytesread = read(infd, iobuffer, CONFIG_LIB_SENDFILE_BUFSIZE);
153
154 /* Check for end of file */
155
156 if (nbytesread == 0)
157 {
158 /* End of file. Break out and return current number of bytes
159 * transferred.
160 */
161
162 endxfr = true;
163 break;
164 }
165
166 /* Check for a read ERROR. EINTR is a special case. This function
167 * should break out and return an error if EINTR is returned and
168 * no data has been transferred. But what should it do if some
169 * data has been transferred? I suppose just continue?
170 */
171
172 else if (nbytesread < 0)
173 {
174 int errcode = get_errno();
175
176 /* EINTR is not an error (but will still stop the copy) */
177
178 if (errcode != EINTR || ntransferred == 0)
179 {
180 /* Read error. Break out and return the error condition. */
181
182 set_errno(errcode);
183 ntransferred = VFS_ERROR;
184 endxfr = true;
185 break;
186 }
187 }
188 }
189 while (nbytesread < 0);
190
191 /* Was anything read? */
192
193 if (!endxfr)
194 {
195 /* Yes.. Loop until the read side of the transfer comes to some
196 * conclusion.
197 */
198
199 wrbuffer = iobuffer;
200 do
201 {
202 /* Write the buffer of data to the outfd */
203
204 nbyteswritten = write(outfd, wrbuffer, nbytesread);
205
206 /* Check for a complete (or parial) write. write() should not
207 * return zero.
208 */
209
210 if (nbyteswritten >= 0)
211 {
212 /* Advance the buffer pointer and decrement the number of bytes
213 * remaining in the iobuffer. Typically, nbytesread will now
214 * be zero.
215 */
216
217 wrbuffer += nbyteswritten;
218 nbytesread -= nbyteswritten;
219
220 /* Increment the total number of bytes successfully transferred. */
221
222 ntransferred += nbyteswritten;
223 }
224
225 /* Otherwise an error occurred */
226
227 else
228 {
229 int errcode = get_errno();
230
231 /* Check for a read ERROR. EINTR is a special case. This
232 * function should break out and return an error if EINTR
233 * is returned and no data has been transferred. But what
234 * should it do if some data has been transferred? I
235 * suppose just continue?
236 */
237
238 if (errcode != EINTR || ntransferred == 0)
239 {
240 /* Write error. Break out and return the error
241 * condition.
242 */
243
244 set_errno(errcode);
245 ntransferred = VFS_ERROR;
246 endxfr = true;
247 break;
248 }
249 }
250 }
251 while (nbytesread > 0);
252 }
253 }
254
255 /* Release the I/O buffer */
256
257 free(iobuffer);
258
259 /* Return the current file position */
260
261 if (offset)
262 {
263 /* Use lseek to get the current file position */
264
265 off_t curpos = lseek(infd, 0, SEEK_CUR);
266 if (curpos == (off_t)-1)
267 {
268 return VFS_ERROR;
269 }
270
271 /* Return the current file position */
272
273 *offset = curpos;
274
275 /* Use lseek again to restore the original file position */
276
277 if (lseek(infd, startpos, SEEK_SET) == (off_t)-1)
278 {
279 return VFS_ERROR;
280 }
281 }
282
283 /* Finally return the number of bytes actually transferred (or VFS_ERROR
284 * if any failure occurred).
285 */
286
287 return ntransferred;
288 }
289
290