buf.c revision 1.1
1/* $NetBSD: buf.c,v 1.1 2022/01/22 07:54:57 pho Exp $ */
2
3/*
4 * Copyright (c) 2021 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote
16 *    products derived from this software without specific prior written
17 *    permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include <sys/cdefs.h>
33#if !defined(lint)
34__RCSID("$NetBSD: buf.c,v 1.1 2022/01/22 07:54:57 pho Exp $");
35#endif /* !lint */
36
37#include <assert.h>
38#include <errno.h>
39#include <fuse_internal.h>
40#include <machine/vmparam.h> /* for PAGE_SIZE */
41#include <stdbool.h>
42#include <stdint.h>
43#include <stdlib.h>
44#include <string.h>
45#include <sys/param.h> /* for MIN(a, b) */
46#include <unistd.h>
47
48size_t
49fuse_buf_size(const struct fuse_bufvec *bufv) {
50    size_t i;
51    size_t total = 0;
52
53    for (i = 0; i < bufv->count; i++) {
54        total += bufv->buf[i].size;
55    }
56
57    return total;
58}
59
60/* Return the pointer to the current buffer in a buffer vector, or
61 * NULL if we have reached the end of the vector. */
62static const struct fuse_buf*
63fuse_buf_current(const struct fuse_bufvec *bufv) {
64    if (bufv->idx < bufv->count)
65        return &bufv->buf[bufv->idx];
66    else
67        return NULL;
68}
69
70/* Copy data from one fd to a memory buffer, and return the number of
71 * octets that have been copied, or -1 on failure. */
72static ssize_t
73fuse_buf_read_fd_to_mem(const struct fuse_buf *dst, size_t dst_off,
74                        const struct fuse_buf *src, size_t src_off,
75                        size_t len) {
76    ssize_t total = 0;
77
78    while (len > 0) {
79        ssize_t n_read;
80
81        if (src->flags & FUSE_BUF_FD_SEEK)
82            n_read = pread(src->fd, (uint8_t*)dst->mem + dst_off, len,
83                           src->pos + (off_t)src_off);
84        else
85            n_read = read(src->fd, (uint8_t*)dst->mem + dst_off, len);
86
87        if (n_read == -1) {
88            if (errno == EINTR)
89                continue;
90            else if (total == 0)
91                return -1;
92            else
93                /* The last pread(2) or read(2) failed but we have
94                 * already copied some data. */
95                break;
96        }
97        else if (n_read == 0) {
98            /* Reached EOF */
99            break;
100        }
101        else {
102            total   += n_read;
103            dst_off += (size_t)n_read;
104            src_off += (size_t)n_read;
105            len     -= (size_t)n_read;
106
107            if (src->flags & FUSE_BUF_FD_RETRY)
108                continue;
109        }
110    }
111
112    return total;
113}
114
115/* Copy data from one memory buffer to an fd, and return the number of
116 * octets that have been copied, or -1 on failure. */
117static ssize_t
118fuse_buf_write_mem_to_fd(const struct fuse_buf *dst, size_t dst_off,
119                         const struct fuse_buf *src, size_t src_off,
120                         size_t len) {
121    ssize_t total = 0;
122
123    while (len > 0) {
124        ssize_t n_wrote;
125
126        if (dst->flags & FUSE_BUF_FD_SEEK)
127            n_wrote = pwrite(dst->fd, (uint8_t*)src->mem + src_off, len,
128                             dst->pos + (off_t)dst_off);
129        else
130            n_wrote = write(dst->fd, (uint8_t*)src->mem + src_off, len);
131
132        if (n_wrote == -1) {
133            if (errno == EINTR)
134                continue;
135            else if (total == 0)
136                return -1;
137            else
138                /* The last pwrite(2) or write(2) failed but we have
139                 * already copied some data. */
140                break;
141        }
142        else if (n_wrote == 0) {
143            break;
144        }
145        else {
146            total   += n_wrote;
147            dst_off += (size_t)n_wrote;
148            src_off += (size_t)n_wrote;
149            len     -= (size_t)n_wrote;
150
151            if (dst->flags & FUSE_BUF_FD_RETRY)
152                continue;
153        }
154    }
155
156    return total;
157}
158
159/* Copy data from one fd to another, and return the number of octets
160 * that have been copied, or -1 on failure. */
161static ssize_t
162fuse_buf_copy_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
163                       const struct fuse_buf *src, size_t src_off,
164                       size_t len) {
165    ssize_t total = 0;
166    struct fuse_buf tmp;
167
168    tmp.size  = PAGE_SIZE;
169    tmp.flags = (enum fuse_buf_flags)0;
170    tmp.mem   = malloc(tmp.size);
171
172    if (tmp.mem == NULL) {
173        return -1;
174    }
175
176    while (len) {
177        size_t n_to_read = MIN(tmp.size, len);
178        ssize_t n_read;
179        ssize_t n_wrote;
180
181        n_read = fuse_buf_read_fd_to_mem(&tmp, 0, src, src_off, n_to_read);
182        if (n_read == -1) {
183            if (total == 0) {
184                free(tmp.mem);
185                return -1;
186            }
187            else {
188                /* We have already copied some data. */
189                break;
190            }
191        }
192        else if (n_read == 0) {
193            /* Reached EOF */
194            break;
195        }
196
197        n_wrote = fuse_buf_write_mem_to_fd(dst, dst_off, &tmp, 0, (size_t)n_read);
198        if (n_wrote == -1) {
199            if (total == 0) {
200                free(tmp.mem);
201                return -1;
202            }
203            else {
204                /* We have already copied some data. */
205                break;
206            }
207        }
208        else if (n_wrote == 0) {
209            break;
210        }
211
212        total   += n_wrote;
213        dst_off += (size_t)n_wrote;
214        src_off += (size_t)n_wrote;
215        len     -= (size_t)n_wrote;
216
217        if (n_wrote < n_read)
218            /* Now we have some data that were read but couldn't be
219             * written, and can't do anything about it. */
220            break;
221    }
222
223    free(tmp.mem);
224    return total;
225}
226
227/* Copy data from one buffer to another, and return the number of
228 * octets that have been copied. */
229static ssize_t
230fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
231                  const struct fuse_buf *src, size_t src_off,
232                  size_t len,
233                  enum fuse_buf_copy_flags flags __attribute__((__unused__))) {
234
235    const bool dst_is_fd = !!(dst->flags & FUSE_BUF_IS_FD);
236    const bool src_is_fd = !!(src->flags & FUSE_BUF_IS_FD);
237
238    if (!dst_is_fd && !src_is_fd) {
239        void* dst_mem = (uint8_t*)dst->mem + dst_off;
240        void* src_mem = (uint8_t*)src->mem + src_off;
241
242        memmove(dst_mem, src_mem, len);
243
244        return (ssize_t)len;
245    }
246    else if (!dst_is_fd) {
247        return fuse_buf_read_fd_to_mem(dst, dst_off, src, src_off, len);
248    }
249    else if (!src_is_fd) {
250        return fuse_buf_write_mem_to_fd(dst, dst_off, src, src_off, len);
251    }
252    else {
253        return fuse_buf_copy_fd_to_fd(dst, dst_off, src, src_off, len);
254    }
255}
256
257/* Advance the buffer by a given number of octets. Return 0 on
258 * success, or -1 otherwise. Reaching the end of the buffer vector
259 * counts as a failure. */
260static int
261fuse_buf_advance(struct fuse_bufvec *bufv, size_t len) {
262    const struct fuse_buf *buf = fuse_buf_current(bufv);
263
264    assert(bufv->off + len <= buf->size);
265    bufv->off += len;
266    if (bufv->off == buf->size) {
267        /* Done with the current buffer. Advance to the next one. */
268        assert(bufv->idx < bufv->count);
269        bufv->idx++;
270        if (bufv->idx == bufv->count)
271            return -1; /* No more buffers in the vector. */
272        bufv->off = 0;
273    }
274    return 0;
275}
276
277ssize_t
278fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
279                      enum fuse_buf_copy_flags flags) {
280    ssize_t total = 0;
281
282    while (true) {
283        const struct fuse_buf* dst;
284        const struct fuse_buf* src;
285        size_t src_len;
286        size_t dst_len;
287        size_t len;
288        ssize_t n_copied;
289
290        dst = fuse_buf_current(dstv);
291        src = fuse_buf_current(srcv);
292        if (src == NULL || dst == NULL)
293            break;
294
295        src_len = src->size - srcv->off;
296        dst_len = dst->size - dstv->off;
297        len = MIN(src_len, dst_len);
298
299        n_copied = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
300        if (n_copied == -1) {
301            if (total == 0)
302                return -1;
303            else
304                /* Failed to copy the current buffer but we have
305                 * already copied some part of the vector. It is
306                 * therefore inappropriate to return an error. */
307                break;
308        }
309        total += n_copied;
310
311        if (fuse_buf_advance(srcv, (size_t)n_copied) != 0 ||
312            fuse_buf_advance(dstv, (size_t)n_copied) != 0)
313            break;
314
315        if ((size_t)n_copied < len)
316            break;
317    }
318
319    return total;
320}
321