1/* $NetBSD: buf.c,v 1.2 2022/01/22 13:25:55 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.2 2022/01/22 13:25:55 pho Exp $");
35#endif /* !lint */
36
37#include <assert.h>
38#include <errno.h>
39#include <fuse_internal.h>
40#include <stdbool.h>
41#include <stdint.h>
42#include <stdlib.h>
43#include <string.h>
44#include <sys/param.h> /* for MIN(a, b) */
45#include <unistd.h>
46
47size_t
48fuse_buf_size(const struct fuse_bufvec *bufv) {
49    size_t i;
50    size_t total = 0;
51
52    for (i = 0; i < bufv->count; i++) {
53        total += bufv->buf[i].size;
54    }
55
56    return total;
57}
58
59/* Return the pointer to the current buffer in a buffer vector, or
60 * NULL if we have reached the end of the vector. */
61static const struct fuse_buf*
62fuse_buf_current(const struct fuse_bufvec *bufv) {
63    if (bufv->idx < bufv->count)
64        return &bufv->buf[bufv->idx];
65    else
66        return NULL;
67}
68
69/* Copy data from one fd to a memory buffer, and return the number of
70 * octets that have been copied, or -1 on failure. */
71static ssize_t
72fuse_buf_read_fd_to_mem(const struct fuse_buf *dst, size_t dst_off,
73                        const struct fuse_buf *src, size_t src_off,
74                        size_t len) {
75    ssize_t total = 0;
76
77    while (len > 0) {
78        ssize_t n_read;
79
80        if (src->flags & FUSE_BUF_FD_SEEK)
81            n_read = pread(src->fd, (uint8_t*)dst->mem + dst_off, len,
82                           src->pos + (off_t)src_off);
83        else
84            n_read = read(src->fd, (uint8_t*)dst->mem + dst_off, len);
85
86        if (n_read == -1) {
87            if (errno == EINTR)
88                continue;
89            else if (total == 0)
90                return -1;
91            else
92                /* The last pread(2) or read(2) failed but we have
93                 * already copied some data. */
94                break;
95        }
96        else if (n_read == 0) {
97            /* Reached EOF */
98            break;
99        }
100        else {
101            total   += n_read;
102            dst_off += (size_t)n_read;
103            src_off += (size_t)n_read;
104            len     -= (size_t)n_read;
105
106            if (src->flags & FUSE_BUF_FD_RETRY)
107                continue;
108        }
109    }
110
111    return total;
112}
113
114/* Copy data from one memory buffer to an fd, and return the number of
115 * octets that have been copied, or -1 on failure. */
116static ssize_t
117fuse_buf_write_mem_to_fd(const struct fuse_buf *dst, size_t dst_off,
118                         const struct fuse_buf *src, size_t src_off,
119                         size_t len) {
120    ssize_t total = 0;
121
122    while (len > 0) {
123        ssize_t n_wrote;
124
125        if (dst->flags & FUSE_BUF_FD_SEEK)
126            n_wrote = pwrite(dst->fd, (uint8_t*)src->mem + src_off, len,
127                             dst->pos + (off_t)dst_off);
128        else
129            n_wrote = write(dst->fd, (uint8_t*)src->mem + src_off, len);
130
131        if (n_wrote == -1) {
132            if (errno == EINTR)
133                continue;
134            else if (total == 0)
135                return -1;
136            else
137                /* The last pwrite(2) or write(2) failed but we have
138                 * already copied some data. */
139                break;
140        }
141        else if (n_wrote == 0) {
142            break;
143        }
144        else {
145            total   += n_wrote;
146            dst_off += (size_t)n_wrote;
147            src_off += (size_t)n_wrote;
148            len     -= (size_t)n_wrote;
149
150            if (dst->flags & FUSE_BUF_FD_RETRY)
151                continue;
152        }
153    }
154
155    return total;
156}
157
158/* Copy data from one fd to another, and return the number of octets
159 * that have been copied, or -1 on failure. */
160static ssize_t
161fuse_buf_copy_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
162                       const struct fuse_buf *src, size_t src_off,
163                       size_t len) {
164    ssize_t total = 0;
165    struct fuse_buf tmp;
166
167    tmp.size  = (size_t)sysconf(_SC_PAGESIZE);
168    tmp.flags = (enum fuse_buf_flags)0;
169    tmp.mem   = malloc(tmp.size);
170
171    if (tmp.mem == NULL) {
172        return -1;
173    }
174
175    while (len) {
176        size_t n_to_read = MIN(tmp.size, len);
177        ssize_t n_read;
178        ssize_t n_wrote;
179
180        n_read = fuse_buf_read_fd_to_mem(&tmp, 0, src, src_off, n_to_read);
181        if (n_read == -1) {
182            if (total == 0) {
183                free(tmp.mem);
184                return -1;
185            }
186            else {
187                /* We have already copied some data. */
188                break;
189            }
190        }
191        else if (n_read == 0) {
192            /* Reached EOF */
193            break;
194        }
195
196        n_wrote = fuse_buf_write_mem_to_fd(dst, dst_off, &tmp, 0, (size_t)n_read);
197        if (n_wrote == -1) {
198            if (total == 0) {
199                free(tmp.mem);
200                return -1;
201            }
202            else {
203                /* We have already copied some data. */
204                break;
205            }
206        }
207        else if (n_wrote == 0) {
208            break;
209        }
210
211        total   += n_wrote;
212        dst_off += (size_t)n_wrote;
213        src_off += (size_t)n_wrote;
214        len     -= (size_t)n_wrote;
215
216        if (n_wrote < n_read)
217            /* Now we have some data that were read but couldn't be
218             * written, and can't do anything about it. */
219            break;
220    }
221
222    free(tmp.mem);
223    return total;
224}
225
226/* Copy data from one buffer to another, and return the number of
227 * octets that have been copied. */
228static ssize_t
229fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
230                  const struct fuse_buf *src, size_t src_off,
231                  size_t len,
232                  enum fuse_buf_copy_flags flags __attribute__((__unused__))) {
233
234    const bool dst_is_fd = !!(dst->flags & FUSE_BUF_IS_FD);
235    const bool src_is_fd = !!(src->flags & FUSE_BUF_IS_FD);
236
237    if (!dst_is_fd && !src_is_fd) {
238        void* dst_mem = (uint8_t*)dst->mem + dst_off;
239        void* src_mem = (uint8_t*)src->mem + src_off;
240
241        memmove(dst_mem, src_mem, len);
242
243        return (ssize_t)len;
244    }
245    else if (!dst_is_fd) {
246        return fuse_buf_read_fd_to_mem(dst, dst_off, src, src_off, len);
247    }
248    else if (!src_is_fd) {
249        return fuse_buf_write_mem_to_fd(dst, dst_off, src, src_off, len);
250    }
251    else {
252        return fuse_buf_copy_fd_to_fd(dst, dst_off, src, src_off, len);
253    }
254}
255
256/* Advance the buffer by a given number of octets. Return 0 on
257 * success, or -1 otherwise. Reaching the end of the buffer vector
258 * counts as a failure. */
259static int
260fuse_buf_advance(struct fuse_bufvec *bufv, size_t len) {
261    const struct fuse_buf *buf = fuse_buf_current(bufv);
262
263    assert(bufv->off + len <= buf->size);
264    bufv->off += len;
265    if (bufv->off == buf->size) {
266        /* Done with the current buffer. Advance to the next one. */
267        assert(bufv->idx < bufv->count);
268        bufv->idx++;
269        if (bufv->idx == bufv->count)
270            return -1; /* No more buffers in the vector. */
271        bufv->off = 0;
272    }
273    return 0;
274}
275
276ssize_t
277fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
278                      enum fuse_buf_copy_flags flags) {
279    ssize_t total = 0;
280
281    while (true) {
282        const struct fuse_buf* dst;
283        const struct fuse_buf* src;
284        size_t src_len;
285        size_t dst_len;
286        size_t len;
287        ssize_t n_copied;
288
289        dst = fuse_buf_current(dstv);
290        src = fuse_buf_current(srcv);
291        if (src == NULL || dst == NULL)
292            break;
293
294        src_len = src->size - srcv->off;
295        dst_len = dst->size - dstv->off;
296        len = MIN(src_len, dst_len);
297
298        n_copied = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
299        if (n_copied == -1) {
300            if (total == 0)
301                return -1;
302            else
303                /* Failed to copy the current buffer but we have
304                 * already copied some part of the vector. It is
305                 * therefore inappropriate to return an error. */
306                break;
307        }
308        total += n_copied;
309
310        if (fuse_buf_advance(srcv, (size_t)n_copied) != 0 ||
311            fuse_buf_advance(dstv, (size_t)n_copied) != 0)
312            break;
313
314        if ((size_t)n_copied < len)
315            break;
316    }
317
318    return total;
319}
320