Second part of fix for bug #8679 - recvfile code path using splice() on Linux leaves...
[samba.git] / source3 / lib / recvfile.c
1 /*
2  Unix SMB/Netbios implementation.
3  Version 3.2.x
4  recvfile implementations.
5  Copyright (C) Jeremy Allison 2007.
6
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 3 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15
16  You should have received a copy of the GNU General Public License
17  along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21  * This file handles the OS dependent recvfile implementations.
22  * The API is such that it returns -1 on error, else returns the
23  * number of bytes written.
24  */
25
26 #include "includes.h"
27 #include "system/filesys.h"
28
29 /* Do this on our own in TRANSFER_BUF_SIZE chunks.
30  * It's safe to make direct syscalls to lseek/write here
31  * as we're below the Samba vfs layer.
32  *
33  * If tofd is -1 we just drain the incoming socket of count
34  * bytes without writing to the outgoing fd.
35  * If a write fails we do the same (to cope with disk full)
36  * errors.
37  *
38  * Returns -1 on short reads from fromfd (read error)
39  * and sets errno.
40  *
41  * Returns number of bytes written to 'tofd'
42  * or thrown away if 'tofd == -1'.
43  * return != count then sets errno.
44  * Returns count if complete success.
45  */
46
47 #ifndef TRANSFER_BUF_SIZE
48 #define TRANSFER_BUF_SIZE (128*1024)
49 #endif
50
51 static ssize_t default_sys_recvfile(int fromfd,
52                         int tofd,
53                         SMB_OFF_T offset,
54                         size_t count)
55 {
56         int saved_errno = 0;
57         size_t total = 0;
58         size_t bufsize = MIN(TRANSFER_BUF_SIZE,count);
59         size_t total_written = 0;
60         char *buffer = NULL;
61
62         DEBUG(10,("default_sys_recvfile: from = %d, to = %d, "
63                 "offset=%.0f, count = %lu\n",
64                 fromfd, tofd, (double)offset,
65                 (unsigned long)count));
66
67         if (count == 0) {
68                 return 0;
69         }
70
71         if (tofd != -1 && offset != (SMB_OFF_T)-1) {
72                 if (sys_lseek(tofd, offset, SEEK_SET) == -1) {
73                         if (errno != ESPIPE) {
74                                 return -1;
75                         }
76                 }
77         }
78
79         buffer = SMB_MALLOC_ARRAY(char, bufsize);
80         if (buffer == NULL) {
81                 return -1;
82         }
83
84         while (total < count) {
85                 size_t num_written = 0;
86                 ssize_t read_ret;
87                 size_t toread = MIN(bufsize,count - total);
88
89                 /* Read from socket - ignore EINTR. */
90                 read_ret = sys_read(fromfd, buffer, toread);
91                 if (read_ret <= 0) {
92                         /* EOF or socket error. */
93                         free(buffer);
94                         return -1;
95                 }
96
97                 num_written = 0;
98
99                 while (num_written < read_ret) {
100                         ssize_t write_ret;
101
102                         if (tofd == -1) {
103                                 write_ret = read_ret;
104                         } else {
105                                 /* Write to file - ignore EINTR. */
106                                 write_ret = sys_write(tofd,
107                                                 buffer + num_written,
108                                                 read_ret - num_written);
109
110                                 if (write_ret <= 0) {
111                                         /* write error - stop writing. */
112                                         tofd = -1;
113                                         saved_errno = errno;
114                                         continue;
115                                 }
116                         }
117
118                         num_written += (size_t)write_ret;
119                         total_written += (size_t)write_ret;
120                 }
121
122                 total += read_ret;
123         }
124
125         free(buffer);
126         if (saved_errno) {
127                 /* Return the correct write error. */
128                 errno = saved_errno;
129         }
130         return (ssize_t)total_written;
131 }
132
133 #if defined(HAVE_LINUX_SPLICE)
134
135 /*
136  * Try and use the Linux system call to do this.
137  * Remember we only return -1 if the socket read
138  * failed. Else we return the number of bytes
139  * actually written. We always read count bytes
140  * from the network in the case of return != -1.
141  */
142
143
144 ssize_t sys_recvfile(int fromfd,
145                         int tofd,
146                         SMB_OFF_T offset,
147                         size_t count)
148 {
149         static int pipefd[2] = { -1, -1 };
150         static bool try_splice_call = false;
151         size_t total_written = 0;
152         loff_t splice_offset = offset;
153
154         DEBUG(10,("sys_recvfile: from = %d, to = %d, "
155                 "offset=%.0f, count = %lu\n",
156                 fromfd, tofd, (double)offset,
157                 (unsigned long)count));
158
159         if (count == 0) {
160                 return 0;
161         }
162
163         /*
164          * Older Linux kernels have splice for sendfile,
165          * but it fails for recvfile. Ensure we only try
166          * this once and always fall back to the userspace
167          * implementation if recvfile splice fails. JRA.
168          */
169
170         if (!try_splice_call) {
171                 return default_sys_recvfile(fromfd,
172                                 tofd,
173                                 offset,
174                                 count);
175         }
176
177         if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) {
178                 try_splice_call = false;
179                 return default_sys_recvfile(fromfd, tofd, offset, count);
180         }
181
182         while (count > 0) {
183                 int nread, to_write;
184
185                 nread = splice(fromfd, NULL, pipefd[1], NULL,
186                                MIN(count, 16384), SPLICE_F_MOVE);
187                 if (nread == -1) {
188                         if (errno == EINTR) {
189                                 continue;
190                         }
191                         if (total_written == 0 &&
192                             (errno == EBADF || errno == EINVAL)) {
193                                 try_splice_call = false;
194                                 return default_sys_recvfile(fromfd, tofd,
195                                                             offset, count);
196                         }
197                         break;
198                 }
199
200                 to_write = nread;
201                 while (to_write > 0) {
202                         int thistime;
203                         thistime = splice(pipefd[0], NULL, tofd,
204                                           &splice_offset, to_write,
205                                           SPLICE_F_MOVE);
206                         if (thistime == -1) {
207                                 goto done;
208                         }
209                         to_write -= thistime;
210                 }
211
212                 total_written += nread;
213                 count -= nread;
214         }
215
216  done:
217         if (count) {
218                 int saved_errno = errno;
219                 if (drain_socket(fromfd, count) != count) {
220                         /* socket is dead. */
221                         return -1;
222                 }
223                 errno = saved_errno;
224         }
225
226         return total_written;
227 }
228 #else
229
230 /*****************************************************************
231  No recvfile system call - use the default 128 chunk implementation.
232 *****************************************************************/
233
234 ssize_t sys_recvfile(int fromfd,
235                         int tofd,
236                         SMB_OFF_T offset,
237                         size_t count)
238 {
239         return default_sys_recvfile(fromfd, tofd, offset, count);
240 }
241 #endif
242
243 /*****************************************************************
244  Throw away "count" bytes from the client socket.
245  Returns count or -1 on error.
246 *****************************************************************/
247
248 ssize_t drain_socket(int sockfd, size_t count)
249 {
250         size_t total = 0;
251         size_t bufsize = MIN(TRANSFER_BUF_SIZE,count);
252         char *buffer = NULL;
253
254         if (count == 0) {
255                 return 0;
256         }
257
258         buffer = SMB_MALLOC_ARRAY(char, bufsize);
259         if (buffer == NULL) {
260                 return -1;
261         }
262
263         while (total < count) {
264                 ssize_t read_ret;
265                 size_t toread = MIN(bufsize,count - total);
266
267                 /* Read from socket - ignore EINTR. */
268                 read_ret = sys_read(sockfd, buffer, toread);
269                 if (read_ret <= 0) {
270                         /* EOF or socket error. */
271                         free(buffer);
272                         return -1;
273                 }
274                 total += read_ret;
275         }
276
277         free(buffer);
278         return count;
279 }