diff --git a/sys/compat/linux/linux_socket.c b/sys/compat/linux/linux_socket.c --- a/sys/compat/linux/linux_socket.c +++ b/sys/compat/linux/linux_socket.c @@ -54,6 +54,8 @@ #include #include #include +#include +#include #include @@ -2058,6 +2060,143 @@ return (error); } + +/* + * Based on sendfile_getsock from kern_sendfile.c + * Determines whether an fd is a stream socket that can be used + * with FreeBSD sendfile. + */ +static bool +_is_stream_socket(struct thread *td, l_int fd) +{ + int error; + struct file *sock_fp = NULL; + struct socket *so = NULL; + + /* + * The socket must be a stream socket and connected. + */ + error = getsock_cap(td, fd, &cap_send_rights, + &sock_fp, NULL, NULL); + if (error != 0) + return false; + so = sock_fp->f_data; + if (so->so_type != SOCK_STREAM) + return false; + /* + * SCTP one-to-one style sockets currently don't work with + * sendfile(). + */ + if (so->so_proto->pr_protocol == IPPROTO_SCTP) + return false; + if (SOLISTENING(so)) + return false; + + return true; +} + +#define TMPBUF_SIZE 8192 + +static int +_sendfile_fallback(struct thread *td, struct file *fp, + struct file *outfp, l_loff_t current_offset, + l_size_t count, off_t *bytes_read) +{ + int error; + struct uio auio; + struct iovec aiov; + void *tmpbuf; + off_t out_offset; + l_size_t bytes_sent; + l_size_t n_read; + + error = (outfp->f_ops->fo_flags & DFLAG_SEEKABLE) != 0 ? + fo_seek(outfp, 0, SEEK_CUR, td) : 0; + + if (error != 0) + return (error); + + out_offset = td->td_uretoff.tdu_off; + + bytes_sent = 0; + tmpbuf = malloc(TMPBUF_SIZE, M_TEMP, M_WAITOK); + + if(!tmpbuf) { + error = ENOMEM; + return (error); + } + + while(bytes_sent < count) { + + off_t to_send = MIN(count - bytes_sent, TMPBUF_SIZE); + + /* + * Read + */ + + aiov.iov_base = tmpbuf; + aiov.iov_len = TMPBUF_SIZE; + + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_td = td; + + auio.uio_rw = UIO_READ; + auio.uio_offset = current_offset + bytes_sent; + auio.uio_resid = to_send; + + error = fo_read(fp, &auio, fp->f_cred, FOF_OFFSET, td); + if (error != 0) { + goto cleanup; + } + + n_read = to_send - auio.uio_resid; + + if(n_read == 0) + break; /* eof */ + + /* + * Write + */ + + aiov.iov_base = tmpbuf; + aiov.iov_len = TMPBUF_SIZE; + + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_segflg = UIO_SYSSPACE; + auio.uio_td = td; + + auio.uio_rw = UIO_WRITE; + + auio.uio_offset = ((outfp->f_ops->fo_flags & DFLAG_SEEKABLE) != 0) ? + out_offset + bytes_sent : + 0; + + auio.uio_resid = n_read; + + error = fo_write(outfp, &auio, outfp->f_cred, FOF_OFFSET, td); + if (error != 0) { + goto cleanup; + } + + bytes_sent += n_read; + } + + if((outfp->f_ops->fo_flags & DFLAG_SEEKABLE) != 0) { + error = fo_seek(outfp, out_offset + bytes_sent, SEEK_SET, td); + if (error != 0) { + goto cleanup; + } + } + +cleanup: + *bytes_read = bytes_sent; + free(tmpbuf, M_TEMP); + return (error); +} + static int linux_sendfile_common(struct thread *td, l_int out, l_int in, l_loff_t *offset, l_size_t count) @@ -2065,13 +2204,18 @@ off_t bytes_read; int error; l_loff_t current_offset; - struct file *fp; + struct file *fp, *outfp; + bool use_sendfile; AUDIT_ARG_FD(in); error = fget_read(td, in, &cap_pread_rights, &fp); if (error != 0) return (error); + use_sendfile = _is_stream_socket(td, out) && + ((fp->f_type == DTYPE_VNODE && fp->f_vnode->v_type == VREG) || + fp->f_type == DTYPE_SHM); + if (offset != NULL) { current_offset = *offset; } else { @@ -2090,10 +2234,21 @@ goto drop; } - error = fo_sendfile(fp, out, NULL, NULL, current_offset, count, - &bytes_read, 0, td); - if (error != 0) + if(use_sendfile) { + error = fo_sendfile(fp, out, NULL, NULL, current_offset, count, + &bytes_read, 0, td); + } else { + error = fget_write(td, out, &cap_pwrite_rights, &outfp); + if (error != 0) + return (error); + error = _sendfile_fallback(td, fp, outfp, current_offset, count, &bytes_read); + fdrop(outfp, td); + } + + if (error != 0) { goto drop; + } + current_offset += bytes_read; if (offset != NULL) { @@ -2121,7 +2276,8 @@ * mean send the whole file.) In linux_sendfile given fds are still * checked for validity when the count is 0. * - Linux can send to any fd whereas FreeBSD only supports sockets. - * The same restriction follows for linux_sendfile. + * We therefore use FreeBSD sendfile where possible for performance, + * but fall back on a manual copy (_sendfile_fallback) * - Linux doesn't have an equivalent for FreeBSD's flags and sf_hdtr. * - Linux takes an offset pointer and updates it to the read location. * FreeBSD takes in an offset and a 'bytes read' parameter which is