summaryrefslogtreecommitdiffstats
path: root/fuse/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'fuse/buffer.c')
-rw-r--r--fuse/buffer.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/fuse/buffer.c b/fuse/buffer.c
new file mode 100644
index 000000000..053e396bc
--- /dev/null
+++ b/fuse/buffer.c
@@ -0,0 +1,318 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_lowlevel.h"
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+{
+ size_t i;
+ size_t size = 0;
+
+ for (i = 0; i < bufv->count; i++) {
+ if (bufv->buf[i].size == SIZE_MAX)
+ size = SIZE_MAX;
+ else
+ size += bufv->buf[i].size;
+ }
+
+ return size;
+}
+
+static size_t min_size(size_t s1, size_t s2)
+{
+ return s1 < s2 ? s1 : s2;
+}
+
+static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ ssize_t res = 0;
+ size_t copied = 0;
+
+ while (len) {
+ if (dst->flags & FUSE_BUF_FD_SEEK) {
+ res = pwrite(dst->fd, src->mem + src_off, len,
+ dst->pos + dst_off);
+ } else {
+ res = write(dst->fd, src->mem + src_off, len);
+ }
+ if (res == -1) {
+ if (!copied)
+ return -errno;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(dst->flags & FUSE_BUF_FD_RETRY))
+ break;
+
+ src_off += res;
+ dst_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ ssize_t res = 0;
+ size_t copied = 0;
+
+ while (len) {
+ if (src->flags & FUSE_BUF_FD_SEEK) {
+ res = pread(src->fd, dst->mem + dst_off, len,
+ src->pos + src_off);
+ } else {
+ res = read(src->fd, dst->mem + dst_off, len);
+ }
+ if (res == -1) {
+ if (!copied)
+ return -errno;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(src->flags & FUSE_BUF_FD_RETRY))
+ break;
+
+ dst_off += res;
+ src_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ char buf[4096];
+ struct fuse_buf tmp = {
+ .size = sizeof(buf),
+ .flags = 0,
+ };
+ ssize_t res;
+ size_t copied = 0;
+
+ tmp.mem = buf;
+
+ while (len) {
+ size_t this_len = min_size(tmp.size, len);
+ size_t read_len;
+
+ res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ read_len = res;
+ res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+
+ if (res < this_len)
+ break;
+
+ dst_off += res;
+ src_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+#ifdef HAVE_SPLICE
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ int splice_flags = 0;
+ off64_t *srcpos = NULL;
+ off64_t *dstpos = NULL;
+ off64_t srcpos_val;
+ off64_t dstpos_val;
+ ssize_t res;
+ size_t copied = 0;
+
+ if (flags & FUSE_BUF_SPLICE_MOVE)
+ splice_flags |= SPLICE_F_MOVE;
+ if (flags & FUSE_BUF_SPLICE_NONBLOCK)
+ splice_flags |= SPLICE_F_NONBLOCK;
+
+ if (src->flags & FUSE_BUF_FD_SEEK) {
+ srcpos_val = src->pos + src_off;
+ srcpos = &srcpos_val;
+ }
+ if (dst->flags & FUSE_BUF_FD_SEEK) {
+ dstpos_val = dst->pos + dst_off;
+ dstpos = &dstpos_val;
+ }
+
+ while (len) {
+ res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+ splice_flags);
+ if (res == -1) {
+ if (copied)
+ break;
+
+ if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+ return -errno;
+
+ /* Maybe splice is not supported for this combination */
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+ len);
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+ !(dst->flags & FUSE_BUF_FD_RETRY)) {
+ break;
+ }
+
+ len -= res;
+ }
+
+ return copied;
+}
+#else
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ (void) flags;
+
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+}
+#endif
+
+
+static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+ int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
+ if (!src_is_fd && !dst_is_fd) {
+ void *dstmem = dst->mem + dst_off;
+ void *srcmem = src->mem + src_off;
+
+ if (dstmem != srcmem) {
+ if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+ memcpy(dstmem, srcmem, len);
+ else
+ memmove(dstmem, srcmem, len);
+ }
+
+ return len;
+ } else if (!src_is_fd) {
+ return fuse_buf_write(dst, dst_off, src, src_off, len);
+ } else if (!dst_is_fd) {
+ return fuse_buf_read(dst, dst_off, src, src_off, len);
+ } else if (flags & FUSE_BUF_NO_SPLICE) {
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+ } else {
+ return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+ }
+}
+
+static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+{
+ if (bufv->idx < bufv->count)
+ return &bufv->buf[bufv->idx];
+ else
+ return NULL;
+}
+
+static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+{
+ const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
+ bufv->off += len;
+ assert(bufv->off <= buf->size);
+ if (bufv->off == buf->size) {
+ assert(bufv->idx < bufv->count);
+ bufv->idx++;
+ if (bufv->idx == bufv->count)
+ return 0;
+ bufv->off = 0;
+ }
+ return 1;
+}
+
+ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ enum fuse_buf_copy_flags flags)
+{
+ size_t copied = 0;
+
+ if (dstv == srcv)
+ return fuse_buf_size(dstv);
+
+ for (;;) {
+ const struct fuse_buf *src = fuse_bufvec_current(srcv);
+ const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+ size_t src_len;
+ size_t dst_len;
+ size_t len;
+ ssize_t res;
+
+ if (src == NULL || dst == NULL)
+ break;
+
+ src_len = src->size - srcv->off;
+ dst_len = dst->size - dstv->off;
+ len = min_size(src_len, dst_len);
+
+ res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ copied += res;
+
+ if (!fuse_bufvec_advance(srcv, res) ||
+ !fuse_bufvec_advance(dstv, res))
+ break;
+
+ if (res < len)
+ break;
+ }
+
+ return copied;
+}