]> arthur.barton.de Git - netatalk.git/blob - libatalk/adouble/ad_recvfile.c
Add recvfile support with splice() on Linux
[netatalk.git] / libatalk / adouble / ad_recvfile.c
1 /*
2  * Copyright (C) Jeremy Allison 2007
3  * Copyright (c) 2013 Ralph Boehme <sloowfranklin@gmail.com>
4  * All rights reserved. See COPYRIGHT.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif /* HAVE_CONFIG_H */
23
24 #ifdef WITH_RECVFILE
25
26 #include <stdio.h>
27 #include <sys/socket.h>
28 #include <sys/uio.h>
29 #include <errno.h>
30 #include <stdlib.h>
31 #include <sys/select.h>
32
33 #include <atalk/adouble.h>
34 #include <atalk/logger.h>
35 #include <atalk/util.h>
36
37 static int ad_recvfile_init(const struct adouble *ad, int eid, off_t *off)
38 {
39     int fd;
40
41     if (eid == ADEID_DFORK) {
42         fd = ad_data_fileno(ad);
43     } else {
44         *off += ad_getentryoff(ad, eid);
45         fd = ad_reso_fileno(ad);
46     }
47
48     return fd;
49 }
50
51 /*
52  * If tofd is -1, drain the incoming socket of count bytes without writing to the outgoing fd,
53  * if a write fails we do the same.
54  *
55  * Returns -1 on short reads from fromfd (read error) and sets errno.
56  *
57  * Returns number of bytes written to 'tofd'  or thrown away if 'tofd == -1'.
58  * return != count then sets errno.
59  * Returns count if complete success.
60  */
61
62 #define TRANSFER_BUF_SIZE (128*1024)
63
64 static ssize_t default_sys_recvfile(int fromfd,
65                                     int tofd,
66                                     off_t offset,
67                                     size_t count)
68 {
69     int saved_errno = 0;
70     size_t total = 0;
71     size_t bufsize = MIN(TRANSFER_BUF_SIZE, count);
72     size_t total_written = 0;
73     char *buffer = NULL;
74
75     if (count == 0) {
76         return 0;
77     }
78
79     LOG(log_maxdebug, logtype_dsi, "default_recvfile: from = %d, to = %d, offset = %.0f, count = %lu\n",
80         fromfd, tofd, (double)offset, (unsigned long)count);
81
82     if ((buffer = malloc(bufsize)) == NULL)
83         return -1;
84
85     while (total < count) {
86         size_t num_written = 0;
87         ssize_t read_ret;
88         size_t toread = MIN(bufsize,count - total);
89
90         /* Read from socket - ignore EINTR. */
91         read_ret = read(fromfd, buffer, toread);
92         if (read_ret <= 0) {
93             /* EOF or socket error. */
94             free(buffer);
95             return -1;
96         }
97
98         num_written = 0;
99
100         while (num_written < read_ret) {
101             ssize_t write_ret;
102
103             if (tofd == -1) {
104                 write_ret = read_ret;
105             } else {
106                 /* Write to file - ignore EINTR. */
107                 write_ret = pwrite(tofd, buffer + num_written, read_ret - num_written, offset);
108                 if (write_ret <= 0) {
109                     /* write error - stop writing. */
110                     tofd = -1;
111                     saved_errno = errno;
112                     continue;
113                 }
114             }
115             num_written += (size_t)write_ret;
116             total_written += (size_t)write_ret;
117         }
118         total += read_ret;
119     }
120
121     free(buffer);
122     if (saved_errno) {
123         /* Return the correct write error. */
124         errno = saved_errno;
125     }
126     return (ssize_t)total_written;
127 }
128
129 #ifdef HAVE_SPLICE
130 static int waitfordata(int socket)
131 {
132     fd_set readfds;
133     int maxfd = socket + 1;
134     int ret;
135
136     FD_ZERO(&readfds);
137
138     while (1) {
139         FD_ZERO(&readfds);
140         FD_SET(socket, &readfds);
141         if ((ret = select(maxfd, &readfds, NULL, NULL, NULL)) <= 0) {
142             if (ret == -1 && errno == EINTR)
143                 continue;
144             LOG(log_error, logtype_dsi, "waitfordata: unexpected select return: %d %s",
145                 ret, ret < 0 ? strerror(errno) : "");
146             return -1;
147         }
148         if (FD_ISSET(socket, &readfds))
149             return 0;
150         return -1;
151     }
152
153 }
154
155 /*
156  * Try and use the Linux system call to do this.
157  * Remember we only return -1 if the socket read
158  * failed. Else we return the number of bytes
159  * actually written. We always read count bytes
160  * from the network in the case of return != -1.
161  */
162 static ssize_t sys_recvfile(int fromfd, int tofd, off_t offset, size_t count, int splice_size)
163 {
164     static int pipefd[2] = { -1, -1 };
165     static bool try_splice_call = true;
166     size_t total_written = 0;
167     loff_t splice_offset = offset;
168
169     LOG(log_debug, logtype_dsi, "sys_recvfile: from = %d, to = %d, offset = %.0f, count = %lu",
170         fromfd, tofd, (double)offset, (unsigned long)count);
171
172     if (count == 0)
173         return 0;
174
175     /*
176      * Older Linux kernels have splice for sendfile,
177      * but it fails for recvfile. Ensure we only try
178      * this once and always fall back to the userspace
179      * implementation if recvfile splice fails. JRA.
180      */
181
182     if (!try_splice_call) {
183         errno = ENOSYS;
184         return -1;
185     }
186
187     if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) {
188         try_splice_call = false;
189         errno = ENOSYS;
190         return -1;
191     }
192
193     while (count > 0) {
194         int nread, to_write;
195
196         nread = splice(fromfd, NULL, pipefd[1], NULL, MIN(count, splice_size), SPLICE_F_MOVE | SPLICE_F_NONBLOCK);
197
198         if (nread == -1) {
199             if (errno == EINTR)
200                 continue;
201             if (errno == EAGAIN) {
202                 if (waitfordata(fromfd) != -1)
203                     continue;
204                 return -1;
205             }
206             if (total_written == 0 && (errno == EBADF || errno == EINVAL)) {
207                 LOG(log_warning, logtype_dsi, "splice() doesn't work for recvfile");
208                 try_splice_call = false;
209                 errno = ENOSYS;
210                 return -1;
211             }
212             break;
213         }
214
215         to_write = nread;
216         while (to_write > 0) {
217             int thistime;
218             thistime = splice(pipefd[0], NULL, tofd, &splice_offset, to_write, SPLICE_F_MOVE);
219             if (thistime == -1)
220                 return -1;
221             to_write -= thistime;
222         }
223
224         total_written += nread;
225         count -= nread;
226     }
227
228 done:
229     LOG(log_maxdebug, logtype_dsi, "sys_recvfile: total_written: %zu", total_written);
230
231     return total_written;
232 }
233 #else
234
235 /*****************************************************************
236  No recvfile system call - use the default 128 chunk implementation.
237 *****************************************************************/
238
239 ssize_t sys_recvfile(int fromfd, int tofd, off_t offset, size_t count)
240 {
241     return default_sys_recvfile(fromfd, tofd, offset, count);
242 }
243 #endif
244
245 /* read from a socket and write to an adouble file */
246 ssize_t ad_recvfile(struct adouble *ad, int eid, int sock, off_t off, size_t len, int splice_size)
247 {
248     ssize_t cc;
249     int fd;
250     off_t off_fork = off;
251
252     fd = ad_recvfile_init(ad, eid, &off_fork);
253     if ((cc = sys_recvfile(sock, fd, off_fork, len, splice_size)) != len)
254         return -1;
255
256     if ((eid != ADEID_DFORK) && (off > ad_getentrylen(ad, eid)))
257         ad_setentrylen(ad, eid, off);
258
259     return cc;
260 }
261 #endif