• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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