diff options
Diffstat (limited to '')
-rw-r--r-- | source3/lib/recvfile.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/source3/lib/recvfile.c b/source3/lib/recvfile.c new file mode 100644 index 0000000..e1eb241 --- /dev/null +++ b/source3/lib/recvfile.c @@ -0,0 +1,310 @@ +/* + Unix SMB/Netbios implementation. + Version 3.2.x + recvfile implementations. + Copyright (C) Jeremy Allison 2007. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + * This file handles the OS dependent recvfile implementations. + * The API is such that it returns -1 on error, else returns the + * number of bytes written. + */ + +#include "includes.h" +#include "system/filesys.h" +#include "lib/util/sys_rw.h" + +/* Do this on our own in TRANSFER_BUF_SIZE chunks. + * It's safe to make direct syscalls to lseek/write here + * as we're below the Samba vfs layer. + * + * Returns -1 on short reads from fromfd (read error) + * and sets errno. + * + * Returns number of bytes written to 'tofd' + * return != count then sets errno. + * Returns count if complete success. + */ + +#ifndef TRANSFER_BUF_SIZE +#define TRANSFER_BUF_SIZE (128*1024) +#endif + +static ssize_t default_sys_recvfile(int fromfd, + int tofd, + off_t offset, + size_t count) +{ + int saved_errno = 0; + size_t total = 0; + size_t bufsize = MIN(TRANSFER_BUF_SIZE,count); + size_t total_written = 0; + char buffer[bufsize]; + + DEBUG(10,("default_sys_recvfile: from = %d, to = %d, " + "offset=%.0f, count = %lu\n", + fromfd, tofd, (double)offset, + (unsigned long)count)); + + if (count == 0) { + return 0; + } + + if (tofd != -1 && offset != (off_t)-1) { + if (lseek(tofd, offset, SEEK_SET) == -1) { + if (errno != ESPIPE) { + return -1; + } + } + } + + while (total < count) { + size_t num_written = 0; + ssize_t read_ret; + size_t toread = MIN(bufsize,count - total); + + /* + * Read from socket - ignore EINTR. + * Can't use sys_read() as that also + * ignores EAGAIN and EWOULDBLOCK. + */ + do { + read_ret = read(fromfd, buffer, toread); + } while (read_ret == -1 && errno == EINTR); + + if (read_ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + /* + * fromfd socket is in non-blocking mode. + * If we already read some and wrote + * it successfully, return that. + * Only return -1 if this is the first read + * attempt. Caller will handle both cases. + */ + if (total_written != 0) { + return total_written; + } + return -1; + } + + if (read_ret <= 0) { + /* EOF or socket error. */ + return -1; + } + + num_written = 0; + + /* Don't write any more after a write error. */ + while (tofd != -1 && (num_written < read_ret)) { + ssize_t write_ret; + + /* Write to file - ignore EINTR. */ + write_ret = sys_write(tofd, + buffer + num_written, + read_ret - num_written); + + if (write_ret <= 0) { + /* write error - stop writing. */ + tofd = -1; + if (total_written == 0) { + /* Ensure we return + -1 if the first + write failed. */ + total_written = -1; + } + saved_errno = errno; + break; + } + + num_written += (size_t)write_ret; + total_written += (size_t)write_ret; + } + + total += read_ret; + } + + if (saved_errno) { + /* Return the correct write error. */ + errno = saved_errno; + } + return (ssize_t)total_written; +} + +#if defined(HAVE_LINUX_SPLICE) + +/* + * Try and use the Linux system call to do this. + * Remember we only return -1 if the socket read + * failed. Else we return the number of bytes + * actually written. We always read count bytes + * from the network in the case of return != -1. + */ + + +ssize_t sys_recvfile(int fromfd, + int tofd, + off_t offset, + size_t count) +{ + static int pipefd[2] = { -1, -1 }; + static bool try_splice_call = false; + size_t total_written = 0; + loff_t splice_offset = offset; + + DEBUG(10,("sys_recvfile: from = %d, to = %d, " + "offset=%.0f, count = %lu\n", + fromfd, tofd, (double)offset, + (unsigned long)count)); + + if (count == 0) { + return 0; + } + + /* + * Older Linux kernels have splice for sendfile, + * but it fails for recvfile. Ensure we only try + * this once and always fall back to the userspace + * implementation if recvfile splice fails. JRA. + */ + + if (!try_splice_call) { + return default_sys_recvfile(fromfd, + tofd, + offset, + count); + } + + if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) { + try_splice_call = false; + return default_sys_recvfile(fromfd, tofd, offset, count); + } + + while (count > 0) { + int nread, to_write; + + nread = splice(fromfd, NULL, pipefd[1], NULL, + MIN(count, 16384), SPLICE_F_MOVE); + if (nread == -1) { + if (errno == EINTR) { + continue; + } + if (total_written == 0 && + (errno == EBADF || errno == EINVAL)) { + try_splice_call = false; + return default_sys_recvfile(fromfd, tofd, + offset, count); + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + /* + * fromfd socket is in non-blocking mode. + * If we already read some and wrote + * it successfully, return that. + * Only return -1 if this is the first read + * attempt. Caller will handle both cases. + */ + if (total_written != 0) { + return total_written; + } + return -1; + } + break; + } + + to_write = nread; + while (to_write > 0) { + int thistime; + thistime = splice(pipefd[0], NULL, tofd, + &splice_offset, to_write, + SPLICE_F_MOVE); + if (thistime == -1) { + goto done; + } + to_write -= thistime; + } + + total_written += nread; + count -= nread; + } + + done: + if (count) { + int saved_errno = errno; + if (drain_socket(fromfd, count) != count) { + /* socket is dead. */ + return -1; + } + errno = saved_errno; + } + + return total_written; +} +#else + +/***************************************************************** + No recvfile system call - use the default 128 chunk implementation. +*****************************************************************/ + +ssize_t sys_recvfile(int fromfd, + int tofd, + off_t offset, + size_t count) +{ + return default_sys_recvfile(fromfd, tofd, offset, count); +} +#endif + +/***************************************************************** + Throw away "count" bytes from the client socket. + Returns count or -1 on error. + Must only operate on a blocking socket. +*****************************************************************/ + +ssize_t drain_socket(int sockfd, size_t count) +{ + size_t total = 0; + size_t bufsize = MIN(TRANSFER_BUF_SIZE,count); + char buffer[bufsize]; + int old_flags = 0; + + if (count == 0) { + return 0; + } + + old_flags = fcntl(sockfd, F_GETFL, 0); + if (set_blocking(sockfd, true) == -1) { + return -1; + } + + while (total < count) { + ssize_t read_ret; + size_t toread = MIN(bufsize,count - total); + + /* Read from socket - ignore EINTR. */ + read_ret = sys_read(sockfd, buffer, toread); + if (read_ret <= 0) { + /* EOF or socket error. */ + count = (size_t)-1; + goto out; + } + total += read_ret; + } + + out: + + if (fcntl(sockfd, F_SETFL, old_flags) == -1) { + return -1; + } + return count; +} |