Imported Upstream version 2.9.0
[platform/upstream/fuse.git] / lib / buffer.c
1 /*
2   FUSE: Filesystem in Userspace
3   Copyright (C) 2010  Miklos Szeredi <miklos@szeredi.hu>
4
5   This program can be distributed under the terms of the GNU LGPLv2.
6   See the file COPYING.LIB
7 */
8
9 #define _GNU_SOURCE
10
11 #include "fuse_i.h"
12 #include "fuse_lowlevel.h"
13 #include <string.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <assert.h>
17
18 size_t fuse_buf_size(const struct fuse_bufvec *bufv)
19 {
20         size_t i;
21         size_t size = 0;
22
23         for (i = 0; i < bufv->count; i++) {
24                 if (bufv->buf[i].size == SIZE_MAX)
25                         size = SIZE_MAX;
26                 else
27                         size += bufv->buf[i].size;
28         }
29
30         return size;
31 }
32
33 static size_t min_size(size_t s1, size_t s2)
34 {
35         return s1 < s2 ? s1 : s2;
36 }
37
38 static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
39                               const struct fuse_buf *src, size_t src_off,
40                               size_t len)
41 {
42         ssize_t res = 0;
43         size_t copied = 0;
44
45         while (len) {
46                 if (dst->flags & FUSE_BUF_FD_SEEK) {
47                         res = pwrite(dst->fd, src->mem + src_off, len,
48                                      dst->pos + dst_off);
49                 } else {
50                         res = write(dst->fd, src->mem + src_off, len);
51                 }
52                 if (res == -1) {
53                         if (!copied)
54                                 return -errno;
55                         break;
56                 }
57                 if (res == 0)
58                         break;
59
60                 copied += res;
61                 if (!(dst->flags & FUSE_BUF_FD_RETRY))
62                         break;
63
64                 src_off += res;
65                 dst_off += res;
66                 len -= res;
67         }
68
69         return copied;
70 }
71
72 static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
73                              const struct fuse_buf *src, size_t src_off,
74                              size_t len)
75 {
76         ssize_t res = 0;
77         size_t copied = 0;
78
79         while (len) {
80                 if (src->flags & FUSE_BUF_FD_SEEK) {
81                         res = pread(src->fd, dst->mem + dst_off, len,
82                                      src->pos + src_off);
83                 } else {
84                         res = read(src->fd, dst->mem + dst_off, len);
85                 }
86                 if (res == -1) {
87                         if (!copied)
88                                 return -errno;
89                         break;
90                 }
91                 if (res == 0)
92                         break;
93
94                 copied += res;
95                 if (!(src->flags & FUSE_BUF_FD_RETRY))
96                         break;
97
98                 dst_off += res;
99                 src_off += res;
100                 len -= res;
101         }
102
103         return copied;
104 }
105
106 static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
107                                  const struct fuse_buf *src, size_t src_off,
108                                  size_t len)
109 {
110         char buf[4096];
111         struct fuse_buf tmp = {
112                 .size = sizeof(buf),
113                 .flags = 0,
114         };
115         ssize_t res;
116         size_t copied = 0;
117
118         tmp.mem = buf;
119
120         while (len) {
121                 size_t this_len = min_size(tmp.size, len);
122                 size_t read_len;
123
124                 res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
125                 if (res < 0) {
126                         if (!copied)
127                                 return res;
128                         break;
129                 }
130                 if (res == 0)
131                         break;
132
133                 read_len = res;
134                 res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
135                 if (res < 0) {
136                         if (!copied)
137                                 return res;
138                         break;
139                 }
140                 if (res == 0)
141                         break;
142
143                 copied += res;
144
145                 if (res < this_len)
146                         break;
147
148                 dst_off += res;
149                 src_off += res;
150                 len -= res;
151         }
152
153         return copied;
154 }
155
156 #ifdef HAVE_SPLICE
157 static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
158                                const struct fuse_buf *src, size_t src_off,
159                                size_t len, enum fuse_buf_copy_flags flags)
160 {
161         int splice_flags = 0;
162         off_t *srcpos = NULL;
163         off_t *dstpos = NULL;
164         off_t srcpos_val;
165         off_t dstpos_val;
166         ssize_t res;
167         size_t copied = 0;
168
169         if (flags & FUSE_BUF_SPLICE_MOVE)
170                 splice_flags |= SPLICE_F_MOVE;
171         if (flags & FUSE_BUF_SPLICE_NONBLOCK)
172                 splice_flags |= SPLICE_F_NONBLOCK;
173
174         if (src->flags & FUSE_BUF_FD_SEEK) {
175                 srcpos_val = src->pos + src_off;
176                 srcpos = &srcpos_val;
177         }
178         if (dst->flags & FUSE_BUF_FD_SEEK) {
179                 dstpos_val = dst->pos + dst_off;
180                 dstpos = &dstpos_val;
181         }
182
183         while (len) {
184                 res = splice(src->fd, srcpos, dst->fd, dstpos, len,
185                              splice_flags);
186                 if (res == -1) {
187                         if (copied)
188                                 break;
189
190                         if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
191                                 return -errno;
192
193                         /* Maybe splice is not supported for this combination */
194                         return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
195                                                  len);
196                 }
197                 if (res == 0)
198                         break;
199
200                 copied += res;
201                 if (!(src->flags & FUSE_BUF_FD_RETRY) &&
202                     !(dst->flags & FUSE_BUF_FD_RETRY)) {
203                         break;
204                 }
205
206                 len -= res;
207         }
208
209         return copied;
210 }
211 #else
212 static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
213                                const struct fuse_buf *src, size_t src_off,
214                                size_t len, enum fuse_buf_copy_flags flags)
215 {
216         (void) flags;
217
218         return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
219 }
220 #endif
221
222
223 static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
224                                  const struct fuse_buf *src, size_t src_off,
225                                  size_t len, enum fuse_buf_copy_flags flags)
226 {
227         int src_is_fd = src->flags & FUSE_BUF_IS_FD;
228         int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
229
230         if (!src_is_fd && !dst_is_fd) {
231                 void *dstmem = dst->mem + dst_off;
232                 void *srcmem = src->mem + src_off;
233
234                 if (dstmem != srcmem) {
235                         if (dstmem + len <= srcmem || srcmem + len <= dstmem)
236                                 memcpy(dstmem, srcmem, len);
237                         else
238                                 memmove(dstmem, srcmem, len);
239                 }
240
241                 return len;
242         } else if (!src_is_fd) {
243                 return fuse_buf_write(dst, dst_off, src, src_off, len);
244         } else if (!dst_is_fd) {
245                 return fuse_buf_read(dst, dst_off, src, src_off, len);
246         } else if (flags & FUSE_BUF_NO_SPLICE) {
247                 return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
248         } else {
249                 return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
250         }
251 }
252
253 static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
254 {
255         if (bufv->idx < bufv->count)
256                 return &bufv->buf[bufv->idx];
257         else
258                 return NULL;
259 }
260
261 static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
262 {
263         const struct fuse_buf *buf = fuse_bufvec_current(bufv);
264
265         bufv->off += len;
266         assert(bufv->off <= buf->size);
267         if (bufv->off == buf->size) {
268                 assert(bufv->idx < bufv->count);
269                 bufv->idx++;
270                 if (bufv->idx == bufv->count)
271                         return 0;
272                 bufv->off = 0;
273         }
274         return 1;
275 }
276
277 ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
278                       enum fuse_buf_copy_flags flags)
279 {
280         size_t copied = 0;
281
282         if (dstv == srcv)
283                 return fuse_buf_size(dstv);
284
285         for (;;) {
286                 const struct fuse_buf *src = fuse_bufvec_current(srcv);
287                 const struct fuse_buf *dst = fuse_bufvec_current(dstv);
288                 size_t src_len;
289                 size_t dst_len;
290                 size_t len;
291                 ssize_t res;
292
293                 if (src == NULL || dst == NULL)
294                         break;
295
296                 src_len = src->size - srcv->off;
297                 dst_len = dst->size - dstv->off;
298                 len = min_size(src_len, dst_len);
299
300                 res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
301                 if (res < 0) {
302                         if (!copied)
303                                 return res;
304                         break;
305                 }
306                 copied += res;
307
308                 if (!fuse_bufvec_advance(srcv, res) ||
309                     !fuse_bufvec_advance(dstv, res))
310                         break;
311
312                 if (res < len)
313                         break;
314         }
315
316         return copied;
317 }