| /**************************************************************************** |
| * fs/vfs/fs_sendfile.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/sendfile.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/net/net.h> |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| static ssize_t copyfile(FAR struct file *outfile, FAR struct file *infile, |
| off_t *offset, size_t count) |
| { |
| FAR uint8_t *iobuffer; |
| FAR uint8_t *wrbuffer; |
| off_t startpos = 0; |
| ssize_t nbytesread; |
| ssize_t nbyteswritten; |
| size_t ntransferred; |
| bool endxfr; |
| |
| /* Get the current file position. */ |
| |
| if (offset) |
| { |
| off_t newpos; |
| |
| /* Use file_seek to get the current file position */ |
| |
| startpos = file_seek(infile, 0, SEEK_CUR); |
| if (startpos < 0) |
| { |
| return startpos; |
| } |
| |
| /* Use file_seek again to set the new file position */ |
| |
| newpos = file_seek(infile, *offset, SEEK_SET); |
| if (newpos < 0) |
| { |
| return newpos; |
| } |
| } |
| |
| /* Allocate an I/O buffer */ |
| |
| iobuffer = kmm_malloc(CONFIG_SENDFILE_BUFSIZE); |
| if (!iobuffer) |
| { |
| return -ENOMEM; |
| } |
| |
| /* Now transfer 'count' bytes from the infile to the outfile */ |
| |
| for (ntransferred = 0, endxfr = false; ntransferred < count && !endxfr; ) |
| { |
| /* Loop until the read side of the transfer comes to some conclusion */ |
| |
| do |
| { |
| /* Read a buffer of data from the infile */ |
| |
| nbytesread = count - ntransferred; |
| if (nbytesread > CONFIG_SENDFILE_BUFSIZE) |
| { |
| nbytesread = CONFIG_SENDFILE_BUFSIZE; |
| } |
| |
| nbytesread = file_read(infile, iobuffer, nbytesread); |
| |
| /* Check for end of file */ |
| |
| if (nbytesread == 0) |
| { |
| /* End of file. Break out and return current number of bytes |
| * transferred. |
| */ |
| |
| endxfr = true; |
| break; |
| } |
| |
| /* Check for a read ERROR. EINTR is a special case. This function |
| * should break out and return an error if EINTR is returned and |
| * no data has been transferred. But what should it do if some |
| * data has been transferred? I suppose just continue? |
| */ |
| |
| else if (nbytesread < 0) |
| { |
| /* EINTR is not an error (but will still stop the copy) */ |
| |
| if (nbytesread != -EINTR || ntransferred == 0) |
| { |
| /* Read error. Break out and return the error condition. */ |
| |
| ntransferred = nbytesread; |
| endxfr = true; |
| break; |
| } |
| } |
| } |
| while (nbytesread < 0); |
| |
| /* Was anything read? */ |
| |
| if (!endxfr) |
| { |
| /* Yes.. Loop until the read side of the transfer comes to some |
| * conclusion. |
| */ |
| |
| wrbuffer = iobuffer; |
| do |
| { |
| /* Write the buffer of data to the outfile */ |
| |
| nbyteswritten = file_write(outfile, wrbuffer, nbytesread); |
| |
| /* Check for a complete (or partial) write. write() should not |
| * return zero. |
| */ |
| |
| if (nbyteswritten >= 0) |
| { |
| /* Advance the buffer pointer and decrement the number of |
| * bytes remaining in the iobuffer. Typically, nbytesread |
| * will now be zero. |
| */ |
| |
| wrbuffer += nbyteswritten; |
| nbytesread -= nbyteswritten; |
| |
| /* Increment the total number of bytes successfully |
| * transferred. |
| */ |
| |
| ntransferred += nbyteswritten; |
| } |
| |
| /* Otherwise an error occurred */ |
| |
| else |
| { |
| /* Check for a write ERROR. EINTR is a special case. This |
| * function should break out and return an error if EINTR |
| * is returned and no data has been transferred. But what |
| * should it do if some data has been transferred? I |
| * suppose just continue? |
| */ |
| |
| if (nbyteswritten != -EINTR || ntransferred == 0) |
| { |
| /* Write error. Break out and return the error |
| * condition. |
| */ |
| |
| ntransferred = nbyteswritten; |
| endxfr = true; |
| break; |
| } |
| } |
| } |
| while (nbytesread > 0); |
| } |
| } |
| |
| /* Release the I/O buffer */ |
| |
| kmm_free(iobuffer); |
| |
| /* Return the current file position */ |
| |
| if (offset) |
| { |
| /* Use file_seek to get the current file position */ |
| |
| off_t curpos = file_seek(infile, 0, SEEK_CUR); |
| if (curpos < 0) |
| { |
| return curpos; |
| } |
| |
| /* Return the current file position */ |
| |
| *offset = curpos; |
| |
| /* Use file_seek again to restore the original file position */ |
| |
| startpos = file_seek(infile, startpos, SEEK_SET); |
| if (startpos < 0) |
| { |
| return startpos; |
| } |
| } |
| |
| /* Finally return the number of bytes actually transferred (or ERROR |
| * if any failure occurred). |
| */ |
| |
| return ntransferred; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: file_sendfile |
| * |
| * Description: |
| * Equivalent to the standard sendfile function except that is accepts a |
| * struct file instance instead of a file descriptor. |
| * |
| ****************************************************************************/ |
| |
| ssize_t file_sendfile(FAR struct file *outfile, FAR struct file *infile, |
| off_t *offset, size_t count) |
| { |
| if (count == 0) |
| { |
| nwarn("WARNING: sendfile count is zero\n"); |
| return 0; |
| } |
| |
| #ifdef CONFIG_NET_SENDFILE |
| /* Check the destination file descriptor: Is it a (probable) file |
| * descriptor? Check the source file: Is it a normal file? |
| */ |
| |
| FAR struct socket *psock; |
| |
| psock = file_socket(outfile); |
| if (psock != NULL) |
| { |
| /* Then let psock_sendfile do the work. */ |
| |
| int ret = psock_sendfile(psock, infile, offset, count); |
| if (ret >= 0 || ret != -ENOSYS) |
| { |
| return ret; |
| } |
| |
| /* Fall back to the slow path if errno equals ENOSYS, |
| * because psock_sendfile fail to optimize this transfer. |
| */ |
| } |
| #endif |
| |
| /* No... then this is probably a file-to-file transfer. The generic |
| * copyfile() can handle that case. |
| */ |
| |
| return copyfile(outfile, infile, offset, count); |
| } |
| |
| /**************************************************************************** |
| * Name: sendfile |
| * |
| * Description: |
| * sendfile() copies data between one file descriptor and another. |
| * Used with file descriptors it basically just wraps a sequence of |
| * reads() and writes() to perform a copy. |
| * |
| * If the destination descriptor is a socket, it gives a better |
| * performance than simple reds() and writes(). The data is read directly |
| * into the net buffer and the whole tcp window is filled if possible. |
| * |
| * NOTE: This interface is *not* specified in POSIX.1-2001, or other |
| * standards. The implementation here is very similar to the Linux |
| * sendfile interface. Other UNIX systems implement sendfile() with |
| * different semantics and prototypes. sendfile() should not be used |
| * in portable programs. |
| * |
| * Input Parameters: |
| * infd - A file (or socket) descriptor opened for reading |
| * outfd - A descriptor opened for writing. |
| * offset - If 'offset' is not NULL, then it points to a variable |
| * holding the file offset from which sendfile() will start |
| * reading data from 'infd'. When sendfile() returns, this |
| * variable will be set to the offset of the byte following |
| * the last byte that was read. If 'offset' is not NULL, |
| * then sendfile() does not modify the current file offset of |
| * 'infd'; otherwise the current file offset is adjusted to |
| * reflect the number of bytes read from 'infd.' |
| * |
| * If 'offset' is NULL, then data will be read from 'infd' |
| * starting at the current file offset, and the file offset |
| * will be updated by the call. |
| * count - The number of bytes to copy between the file descriptors. |
| * |
| * Returned Value: |
| * If the transfer was successful, the number of bytes written to outfd is |
| * returned. On error, -1 is returned, and errno is set appropriately. |
| * There error values are those returned by read() or write() plus: |
| * |
| * EINVAL - Bad input parameters. |
| * ENOMEM - Could not allocated an I/O buffer |
| * |
| ****************************************************************************/ |
| |
| ssize_t sendfile(int outfd, int infd, off_t *offset, size_t count) |
| { |
| FAR struct file *outfile; |
| FAR struct file *infile; |
| int ret; |
| |
| ret = fs_getfilep(outfd, &outfile); |
| if (ret < 0) |
| { |
| goto errout; |
| } |
| |
| ret = fs_getfilep(infd, &infile); |
| if (ret < 0) |
| { |
| goto errout; |
| } |
| |
| ret = file_sendfile(outfile, infile, offset, count); |
| if (ret < 0) |
| { |
| goto errout; |
| } |
| |
| return ret; |
| |
| errout: |
| set_errno(-ret); |
| return ERROR; |
| } |