Add write buffer for tar writes
update fuse to 2.9.2
catch return from unlink so that we don't print error messages when things work
Change-Id: I1115039a0fa5d9d73f78ef1abd79755d7ffd9d96
diff --git a/fuse/Android.mk b/fuse/Android.mk
index 0855959..59cd9b3 100644
--- a/fuse/Android.mk
+++ b/fuse/Android.mk
@@ -17,6 +17,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
+ buffer.c \
cuse_lowlevel.c \
fuse.c \
fuse_kern_chan.c \
diff --git a/fuse/buffer.c b/fuse/buffer.c
new file mode 100644
index 0000000..053e396
--- /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;
+}
diff --git a/fuse/cuse_lowlevel.c b/fuse/cuse_lowlevel.c
index 970df7f..be49ad4 100644
--- a/fuse/cuse_lowlevel.c
+++ b/fuse/cuse_lowlevel.c
@@ -214,14 +214,14 @@
f->conn.want = 0;
if (arg->major < 7) {
- fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n",
+ fprintf(stderr, "cuse: unsupported protocol version: %u.%u\n",
arg->major, arg->minor);
fuse_reply_err(req, EPROTO);
return;
}
if (bufsize < FUSE_MIN_READ_BUFFER) {
- fprintf(stderr, "fuse: warning: buffer size too small: %zu\n",
+ fprintf(stderr, "cuse: warning: buffer size too small: %zu\n",
bufsize);
bufsize = FUSE_MIN_READ_BUFFER;
}
@@ -306,9 +306,9 @@
fd = open(devname, O_RDWR);
if (fd == -1) {
if (errno == ENODEV || errno == ENOENT)
- fprintf(stderr, "fuse: device not found, try 'modprobe cuse' first\n");
+ fprintf(stderr, "cuse: device not found, try 'modprobe cuse' first\n");
else
- fprintf(stderr, "fuse: failed to open %s: %s\n",
+ fprintf(stderr, "cuse: failed to open %s: %s\n",
devname, strerror(errno));
goto err_se;
}
diff --git a/fuse/fuse.c b/fuse/fuse.c
index 98170cf..34b11d4 100644
--- a/fuse/fuse.c
+++ b/fuse/fuse.c
@@ -10,6 +10,7 @@
/* For pthread_rwlock_t */
#define _GNU_SOURCE
+#include "config.h"
#include "fuse_i.h"
#include "fuse_lowlevel.h"
#include "fuse_opt.h"
@@ -22,6 +23,7 @@
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
+#include <stdbool.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
@@ -30,15 +32,25 @@
#include <signal.h>
#include <dlfcn.h>
#include <assert.h>
+#include <poll.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <sys/time.h>
+#include <sys/mman.h>
+
+#define FUSE_NODE_SLAB 1
+
+#ifndef MAP_ANONYMOUS
+#undef FUSE_NODE_SLAB
+#endif
#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
#define FUSE_UNKNOWN_INO 0xffffffff
#define OFFSET_MAX 0x7fffffffffffffffLL
+#define NODE_TABLE_MIN_SIZE 8192
+
struct fuse_config {
unsigned int uid;
unsigned int gid;
@@ -48,7 +60,8 @@
double attr_timeout;
double ac_attr_timeout;
int ac_attr_timeout_set;
- int noforget;
+ int remember;
+ int nopath;
int debug;
int hard_remove;
int use_ino;
@@ -79,16 +92,52 @@
};
struct lock_queue_element {
- struct lock_queue_element *next;
- pthread_cond_t cond;
+ struct lock_queue_element *next;
+ pthread_cond_t cond;
+ fuse_ino_t nodeid1;
+ const char *name1;
+ char **path1;
+ struct node **wnode1;
+ fuse_ino_t nodeid2;
+ const char *name2;
+ char **path2;
+ struct node **wnode2;
+ int err;
+ bool first_locked : 1;
+ bool second_locked : 1;
+ bool done : 1;
+};
+
+struct node_table {
+ struct node **array;
+ size_t use;
+ size_t size;
+ size_t split;
+};
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+struct list_head {
+ struct list_head *next;
+ struct list_head *prev;
+};
+
+struct node_slab {
+ struct list_head list; /* must be the first member */
+ struct list_head freelist;
+ int used;
};
struct fuse {
struct fuse_session *se;
- struct node **name_table;
- size_t name_table_size;
- struct node **id_table;
- size_t id_table_size;
+ struct node_table name_table;
+ struct node_table id_table;
+ struct list_head lru_table;
fuse_ino_t ctr;
unsigned int generation;
unsigned int hidectr;
@@ -97,8 +146,12 @@
int intr_installed;
struct fuse_fs *fs;
int nullpath_ok;
- int curr_ticket;
+ int utime_omit_ok;
struct lock_queue_element *lockq;
+ int pagesize;
+ struct list_head partial_slabs;
+ struct list_head full_slabs;
+ pthread_t prune_thread;
};
struct lock {
@@ -127,7 +180,16 @@
unsigned int is_hidden : 1;
unsigned int cache_valid : 1;
int treelock;
- int ticket;
+ char inline_name[32];
+};
+
+#define TREELOCK_WRITE -1
+#define TREELOCK_WAIT_OFFSET INT_MIN
+
+struct node_lru {
+ struct node node;
+ struct list_head lru;
+ struct timespec forget_time;
};
struct fuse_dh {
@@ -258,12 +320,185 @@
pthread_mutex_unlock(&fuse_context_lock);
}
+static void init_list_head(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+static void list_add(struct list_head *new, struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+static inline void list_add_head(struct list_head *new, struct list_head *head)
+{
+ list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ list_add(new, head->prev, head);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+ struct list_head *prev = entry->prev;
+ struct list_head *next = entry->next;
+
+ next->prev = prev;
+ prev->next = next;
+}
+
+static inline int lru_enabled(struct fuse *f)
+{
+ return f->conf.remember > 0;
+}
+
+static struct node_lru *node_lru(struct node *node)
+{
+ return (struct node_lru *) node;
+}
+
+static size_t get_node_size(struct fuse *f)
+{
+ if (lru_enabled(f))
+ return sizeof(struct node_lru);
+ else
+ return sizeof(struct node);
+}
+
+#ifdef FUSE_NODE_SLAB
+static struct node_slab *list_to_slab(struct list_head *head)
+{
+ return (struct node_slab *) head;
+}
+
+static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+{
+ return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+}
+
+static int alloc_slab(struct fuse *f)
+{
+ void *mem;
+ struct node_slab *slab;
+ char *start;
+ size_t num;
+ size_t i;
+ size_t node_size = get_node_size(f);
+
+ mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (mem == MAP_FAILED)
+ return -1;
+
+ slab = mem;
+ init_list_head(&slab->freelist);
+ slab->used = 0;
+ num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
+ start = (char *) mem + f->pagesize - num * node_size;
+ for (i = 0; i < num; i++) {
+ struct list_head *n;
+
+ n = (struct list_head *) (start + i * node_size);
+ list_add_tail(n, &slab->freelist);
+ }
+ list_add_tail(&slab->list, &f->partial_slabs);
+
+ return 0;
+}
+
+static struct node *alloc_node(struct fuse *f)
+{
+ struct node_slab *slab;
+ struct list_head *node;
+
+ if (list_empty(&f->partial_slabs)) {
+ int res = alloc_slab(f);
+ if (res != 0)
+ return NULL;
+ }
+ slab = list_to_slab(f->partial_slabs.next);
+ slab->used++;
+ node = slab->freelist.next;
+ list_del(node);
+ if (list_empty(&slab->freelist)) {
+ list_del(&slab->list);
+ list_add_tail(&slab->list, &f->full_slabs);
+ }
+ memset(node, 0, sizeof(struct node));
+
+ return (struct node *) node;
+}
+
+static void free_slab(struct fuse *f, struct node_slab *slab)
+{
+ int res;
+
+ list_del(&slab->list);
+ res = munmap(slab, f->pagesize);
+ if (res == -1)
+ fprintf(stderr, "fuse warning: munmap(%p) failed\n", slab);
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+ struct node_slab *slab = node_to_slab(f, node);
+ struct list_head *n = (struct list_head *) node;
+
+ slab->used--;
+ if (slab->used) {
+ if (list_empty(&slab->freelist)) {
+ list_del(&slab->list);
+ list_add_tail(&slab->list, &f->partial_slabs);
+ }
+ list_add_head(n, &slab->freelist);
+ } else {
+ free_slab(f, slab);
+ }
+}
+#else
+static struct node *alloc_node(struct fuse *f)
+{
+ return (struct node *) calloc(1, get_node_size(f));
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+ (void) f;
+ free(node);
+}
+#endif
+
+static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+{
+ uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+ uint64_t oldhash = hash % (f->id_table.size / 2);
+
+ if (oldhash >= f->id_table.split)
+ return oldhash;
+ else
+ return hash;
+}
+
static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
{
- size_t hash = nodeid % f->id_table_size;
+ size_t hash = id_hash(f, nodeid);
struct node *node;
- for (node = f->id_table[hash]; node != NULL; node = node->id_next)
+ for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
if (node->nodeid == nodeid)
return node;
@@ -281,59 +516,211 @@
return node;
}
-static void free_node(struct node *node)
+static void curr_time(struct timespec *now);
+static double diff_timespec(const struct timespec *t1,
+ const struct timespec *t2);
+
+static void remove_node_lru(struct node *node)
{
- free(node->name);
- free(node);
+ struct node_lru *lnode = node_lru(node);
+ list_del(&lnode->lru);
+ init_list_head(&lnode->lru);
+}
+
+static void set_forget_time(struct fuse *f, struct node *node)
+{
+ struct node_lru *lnode = node_lru(node);
+
+ list_del(&lnode->lru);
+ list_add_tail(&lnode->lru, &f->lru_table);
+ curr_time(&lnode->forget_time);
+}
+
+static void free_node(struct fuse *f, struct node *node)
+{
+ if (node->name != node->inline_name)
+ free(node->name);
+ free_node_mem(f, node);
+}
+
+static void node_table_reduce(struct node_table *t)
+{
+ size_t newsize = t->size / 2;
+ void *newarray;
+
+ if (newsize < NODE_TABLE_MIN_SIZE)
+ return;
+
+ newarray = realloc(t->array, sizeof(struct node *) * newsize);
+ if (newarray != NULL)
+ t->array = newarray;
+
+ t->size = newsize;
+ t->split = t->size / 2;
+}
+
+static void remerge_id(struct fuse *f)
+{
+ struct node_table *t = &f->id_table;
+ int iter;
+
+ if (t->split == 0)
+ node_table_reduce(t);
+
+ for (iter = 8; t->split > 0 && iter; iter--) {
+ struct node **upper;
+
+ t->split--;
+ upper = &t->array[t->split + t->size / 2];
+ if (*upper) {
+ struct node **nodep;
+
+ for (nodep = &t->array[t->split]; *nodep;
+ nodep = &(*nodep)->id_next);
+
+ *nodep = *upper;
+ *upper = NULL;
+ break;
+ }
+ }
}
static void unhash_id(struct fuse *f, struct node *node)
{
- size_t hash = node->nodeid % f->id_table_size;
- struct node **nodep = &f->id_table[hash];
+ struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
for (; *nodep != NULL; nodep = &(*nodep)->id_next)
if (*nodep == node) {
*nodep = node->id_next;
+ f->id_table.use--;
+
+ if(f->id_table.use < f->id_table.size / 4)
+ remerge_id(f);
return;
}
}
-static void hash_id(struct fuse *f, struct node *node)
+static int node_table_resize(struct node_table *t)
{
- size_t hash = node->nodeid % f->id_table_size;
- node->id_next = f->id_table[hash];
- f->id_table[hash] = node;
+ size_t newsize = t->size * 2;
+ void *newarray;
+
+ newarray = realloc(t->array, sizeof(struct node *) * newsize);
+ if (newarray == NULL)
+ return -1;
+
+ t->array = newarray;
+ memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+ t->size = newsize;
+ t->split = 0;
+
+ return 0;
}
-static unsigned int name_hash(struct fuse *f, fuse_ino_t parent,
- const char *name)
+static void rehash_id(struct fuse *f)
{
- unsigned int hash = *name;
+ struct node_table *t = &f->id_table;
+ struct node **nodep;
+ struct node **next;
+ size_t hash;
- if (hash)
- for (name += 1; *name != '\0'; name++)
- hash = (hash << 5) - hash + *name;
+ if (t->split == t->size / 2)
+ return;
- return (hash + parent) % f->name_table_size;
+ hash = t->split;
+ t->split++;
+ for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+ struct node *node = *nodep;
+ size_t newhash = id_hash(f, node->nodeid);
+
+ if (newhash != hash) {
+ next = nodep;
+ *nodep = node->id_next;
+ node->id_next = t->array[newhash];
+ t->array[newhash] = node;
+ } else {
+ next = &node->id_next;
+ }
+ }
+ if (t->split == t->size / 2)
+ node_table_resize(t);
+}
+
+static void hash_id(struct fuse *f, struct node *node)
+{
+ size_t hash = id_hash(f, node->nodeid);
+ node->id_next = f->id_table.array[hash];
+ f->id_table.array[hash] = node;
+ f->id_table.use++;
+
+ if (f->id_table.use >= f->id_table.size / 2)
+ rehash_id(f);
+}
+
+static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+ const char *name)
+{
+ uint64_t hash = parent;
+ uint64_t oldhash;
+
+ for (; *name; name++)
+ hash = hash * 31 + (unsigned char) *name;
+
+ hash %= f->name_table.size;
+ oldhash = hash % (f->name_table.size / 2);
+ if (oldhash >= f->name_table.split)
+ return oldhash;
+ else
+ return hash;
}
static void unref_node(struct fuse *f, struct node *node);
+static void remerge_name(struct fuse *f)
+{
+ struct node_table *t = &f->name_table;
+ int iter;
+
+ if (t->split == 0)
+ node_table_reduce(t);
+
+ for (iter = 8; t->split > 0 && iter; iter--) {
+ struct node **upper;
+
+ t->split--;
+ upper = &t->array[t->split + t->size / 2];
+ if (*upper) {
+ struct node **nodep;
+
+ for (nodep = &t->array[t->split]; *nodep;
+ nodep = &(*nodep)->name_next);
+
+ *nodep = *upper;
+ *upper = NULL;
+ break;
+ }
+ }
+}
+
static void unhash_name(struct fuse *f, struct node *node)
{
if (node->name) {
size_t hash = name_hash(f, node->parent->nodeid, node->name);
- struct node **nodep = &f->name_table[hash];
+ struct node **nodep = &f->name_table.array[hash];
for (; *nodep != NULL; nodep = &(*nodep)->name_next)
if (*nodep == node) {
*nodep = node->name_next;
node->name_next = NULL;
unref_node(f, node->parent);
- free(node->name);
+ if (node->name != node->inline_name)
+ free(node->name);
node->name = NULL;
node->parent = NULL;
+ f->name_table.use--;
+
+ if (f->name_table.use < f->name_table.size / 4)
+ remerge_name(f);
return;
}
fprintf(stderr,
@@ -343,19 +730,58 @@
}
}
+static void rehash_name(struct fuse *f)
+{
+ struct node_table *t = &f->name_table;
+ struct node **nodep;
+ struct node **next;
+ size_t hash;
+
+ if (t->split == t->size / 2)
+ return;
+
+ hash = t->split;
+ t->split++;
+ for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+ struct node *node = *nodep;
+ size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
+ if (newhash != hash) {
+ next = nodep;
+ *nodep = node->name_next;
+ node->name_next = t->array[newhash];
+ t->array[newhash] = node;
+ } else {
+ next = &node->name_next;
+ }
+ }
+ if (t->split == t->size / 2)
+ node_table_resize(t);
+}
+
static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
const char *name)
{
size_t hash = name_hash(f, parentid, name);
struct node *parent = get_node(f, parentid);
- node->name = strdup(name);
- if (node->name == NULL)
- return -1;
+ if (strlen(name) < sizeof(node->inline_name)) {
+ strcpy(node->inline_name, name);
+ node->name = node->inline_name;
+ } else {
+ node->name = strdup(name);
+ if (node->name == NULL)
+ return -1;
+ }
parent->refctr ++;
node->parent = parent;
- node->name_next = f->name_table[hash];
- f->name_table[hash] = node;
+ node->name_next = f->name_table.array[hash];
+ f->name_table.array[hash] = node;
+ f->name_table.use++;
+
+ if (f->name_table.use >= f->name_table.size / 2)
+ rehash_name(f);
+
return 0;
}
@@ -366,9 +792,11 @@
(unsigned long long) node->nodeid);
assert(node->treelock == 0);
- assert(!node->name);
+ unhash_name(f, node);
+ if (lru_enabled(f))
+ remove_node_lru(node);
unhash_id(f, node);
- free_node(node);
+ free_node(f, node);
}
static void unref_node(struct fuse *f, struct node *node)
@@ -396,7 +824,7 @@
size_t hash = name_hash(f, parent, name);
struct node *node;
- for (node = f->name_table[hash]; node != NULL; node = node->name_next)
+ for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
if (node->parent->nodeid == parent &&
strcmp(node->name, name) == 0)
return node;
@@ -404,6 +832,13 @@
return NULL;
}
+static void inc_nlookup(struct node *node)
+{
+ if (!node->nlookup)
+ node->refctr++;
+ node->nlookup++;
+}
+
static struct node *find_node(struct fuse *f, fuse_ino_t parent,
const char *name)
{
@@ -415,27 +850,29 @@
else
node = lookup_node(f, parent, name);
if (node == NULL) {
- node = (struct node *) calloc(1, sizeof(struct node));
+ node = alloc_node(f);
if (node == NULL)
goto out_err;
- if (f->conf.noforget)
- node->nlookup = 1;
- node->refctr = 1;
node->nodeid = next_id(f);
node->generation = f->generation;
- node->open_count = 0;
- node->is_hidden = 0;
- node->treelock = 0;
- node->ticket = 0;
+ if (f->conf.remember)
+ inc_nlookup(node);
+
if (hash_name(f, node, parent, name) == -1) {
- free(node);
+ free_node(f, node);
node = NULL;
goto out_err;
}
hash_id(f, node);
+ if (lru_enabled(f)) {
+ struct node_lru *lnode = node_lru(node);
+ init_list_head(&lnode->lru);
+ }
+ } else if (lru_enabled(f) && node->nlookup == 1) {
+ remove_node_lru(node);
}
- node->nlookup ++;
+ inc_nlookup(node);
out_err:
pthread_mutex_unlock(&f->lock);
return node;
@@ -475,28 +912,28 @@
}
static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
- struct node *end, int ticket)
+ struct node *end)
{
struct node *node;
if (wnode) {
- assert(wnode->treelock == -1);
+ assert(wnode->treelock == TREELOCK_WRITE);
wnode->treelock = 0;
- if (!wnode->ticket)
- wnode->ticket = ticket;
}
for (node = get_node(f, nodeid);
node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
- assert(node->treelock > 0);
+ assert(node->treelock != 0);
+ assert(node->treelock != TREELOCK_WAIT_OFFSET);
+ assert(node->treelock != TREELOCK_WRITE);
node->treelock--;
- if (!node->ticket)
- node->ticket = ticket;
+ if (node->treelock == TREELOCK_WAIT_OFFSET)
+ node->treelock = 0;
}
}
static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
- char **path, struct node **wnodep, int ticket)
+ char **path, struct node **wnodep, bool need_lock)
{
unsigned bufsize = 256;
char *buf;
@@ -507,9 +944,10 @@
*path = NULL;
+ err = -ENOMEM;
buf = malloc(bufsize);
if (buf == NULL)
- return -ENOMEM;
+ goto out_err;
s = buf + bufsize - 1;
*s = '\0';
@@ -517,51 +955,41 @@
if (name != NULL) {
s = add_name(&buf, &bufsize, s, name);
err = -ENOMEM;
- printf("setting err to ENOMEM\n");
if (s == NULL)
goto out_free;
}
if (wnodep) {
- assert(ticket);
+ assert(need_lock);
wnode = lookup_node(f, nodeid, name);
if (wnode) {
- if (wnode->treelock != 0 ||
- (wnode->ticket && wnode->ticket != ticket)) {
- if (!wnode->ticket)
- wnode->ticket = ticket;
+ if (wnode->treelock != 0) {
+ if (wnode->treelock > 0)
+ wnode->treelock += TREELOCK_WAIT_OFFSET;
err = -EAGAIN;
- printf("setting err to EAGAIN\n");
goto out_free;
}
- wnode->treelock = -1;
- wnode->ticket = 0;
+ wnode->treelock = TREELOCK_WRITE;
}
}
- err = 0;
for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
node = node->parent) {
err = -ENOENT;
- printf("setting err to ENOENT\n");
if (node->name == NULL || node->parent == NULL)
goto out_unlock;
err = -ENOMEM;
- printf("setting err to ENOMEM\n");
s = add_name(&buf, &bufsize, s, node->name);
if (s == NULL)
goto out_unlock;
- if (ticket) {
+ if (need_lock) {
err = -EAGAIN;
- printf("setting err to EAGAIN\n");
- if (node->treelock == -1 ||
- (node->ticket && node->ticket != ticket))
+ if (node->treelock < 0)
goto out_unlock;
node->treelock++;
- node->ticket = 0;
}
}
@@ -577,36 +1005,95 @@
return 0;
out_unlock:
- if (ticket)
- unlock_path(f, nodeid, wnode, node, ticket);
+ if (need_lock)
+ unlock_path(f, nodeid, wnode, node);
out_free:
free(buf);
+ out_err:
return err;
}
-static void wake_up_first(struct fuse *f)
+static void queue_element_unlock(struct fuse *f, struct lock_queue_element *qe)
{
- if (f->lockq)
- pthread_cond_signal(&f->lockq->cond);
+ struct node *wnode;
+
+ if (qe->first_locked) {
+ wnode = qe->wnode1 ? *qe->wnode1 : NULL;
+ unlock_path(f, qe->nodeid1, wnode, NULL);
+ }
+ if (qe->second_locked) {
+ wnode = qe->wnode2 ? *qe->wnode2 : NULL;
+ unlock_path(f, qe->nodeid2, wnode, NULL);
+ }
}
-static void wake_up_next(struct lock_queue_element *qe)
+static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
{
- if (qe->next)
- pthread_cond_signal(&qe->next->cond);
+ int err;
+ bool first = (qe == f->lockq);
+
+ if (!qe->path1) {
+ /* Just waiting for it to be unlocked */
+ if (get_node(f, qe->nodeid1)->treelock == 0)
+ pthread_cond_signal(&qe->cond);
+
+ return;
+ }
+
+ if (!qe->first_locked) {
+ err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+ qe->wnode1, true);
+ if (!err)
+ qe->first_locked = true;
+ else if (err != -EAGAIN)
+ goto err_unlock;
+ }
+ if (!qe->second_locked && qe->path2) {
+ err = try_get_path(f, qe->nodeid2, qe->name2, qe->path2,
+ qe->wnode2, true);
+ if (!err)
+ qe->second_locked = true;
+ else if (err != -EAGAIN)
+ goto err_unlock;
+ }
+
+ if (qe->first_locked && (qe->second_locked || !qe->path2)) {
+ err = 0;
+ goto done;
+ }
+
+ /*
+ * Only let the first element be partially locked otherwise there could
+ * be a deadlock.
+ *
+ * But do allow the first element to be partially locked to prevent
+ * starvation.
+ */
+ if (!first)
+ queue_element_unlock(f, qe);
+
+ /* keep trying */
+ return;
+
+err_unlock:
+ queue_element_unlock(f, qe);
+done:
+ qe->err = err;
+ qe->done = true;
+ pthread_cond_signal(&qe->cond);
}
-static int get_ticket(struct fuse *f)
+static void wake_up_queued(struct fuse *f)
{
- do f->curr_ticket++;
- while (f->curr_ticket == 0);
+ struct lock_queue_element *qe;
- return f->curr_ticket;
+ for (qe = f->lockq; qe != NULL; qe = qe->next)
+ queue_element_wakeup(f, qe);
}
static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
- const char *name, int wr)
+ const char *name, bool wr)
{
if (f->conf.debug) {
struct node *wnode = NULL;
@@ -621,56 +1108,58 @@
}
}
-static void queue_path(struct fuse *f, struct lock_queue_element *qe,
- fuse_ino_t nodeid, const char *name, int wr)
+static void queue_path(struct fuse *f, struct lock_queue_element *qe)
{
struct lock_queue_element **qp;
- debug_path(f, "QUEUE PATH", nodeid, name, wr);
+ qe->done = false;
+ qe->first_locked = false;
+ qe->second_locked = false;
pthread_cond_init(&qe->cond, NULL);
qe->next = NULL;
for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
*qp = qe;
}
-static void dequeue_path(struct fuse *f, struct lock_queue_element *qe,
- fuse_ino_t nodeid, const char *name, int wr)
+static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
{
struct lock_queue_element **qp;
- debug_path(f, "DEQUEUE PATH", nodeid, name, wr);
pthread_cond_destroy(&qe->cond);
for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
*qp = qe->next;
}
-static void wait_on_path(struct fuse *f, struct lock_queue_element *qe,
- fuse_ino_t nodeid, const char *name, int wr)
+static int wait_path(struct fuse *f, struct lock_queue_element *qe)
{
- debug_path(f, "WAIT ON PATH", nodeid, name, wr);
- pthread_cond_wait(&qe->cond, &f->lock);
+ queue_path(f, qe);
+
+ do {
+ pthread_cond_wait(&qe->cond, &f->lock);
+ } while (!qe->done);
+
+ dequeue_path(f, qe);
+
+ return qe->err;
}
static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
char **path, struct node **wnode)
{
int err;
- int ticket;
pthread_mutex_lock(&f->lock);
- ticket = get_ticket(f);
- err = try_get_path(f, nodeid, name, path, wnode, ticket);
+ err = try_get_path(f, nodeid, name, path, wnode, true);
if (err == -EAGAIN) {
- struct lock_queue_element qe;
-
- queue_path(f, &qe, nodeid, name, !!wnode);
- do {
- wait_on_path(f, &qe, nodeid, name, !!wnode);
- err = try_get_path(f, nodeid, name, path, wnode,
- ticket);
- wake_up_next(&qe);
- } while (err == -EAGAIN);
- dequeue_path(f, &qe, nodeid, name, !!wnode);
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid,
+ .name1 = name,
+ .path1 = path,
+ .wnode1 = wnode,
+ };
+ debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+ err = wait_path(f, &qe);
+ debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
}
pthread_mutex_unlock(&f->lock);
@@ -684,10 +1173,15 @@
static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
{
- int err = get_path_common(f, nodeid, NULL, path, NULL);
+ int err = 0;
- if (err == -ENOENT && f->nullpath_ok)
- err = 0;
+ if (f->conf.nopath) {
+ *path = NULL;
+ } else {
+ err = get_path_common(f, nodeid, NULL, path, NULL);
+ if (err == -ENOENT && f->nullpath_ok)
+ err = 0;
+ }
return err;
}
@@ -707,18 +1201,20 @@
static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
fuse_ino_t nodeid2, const char *name2,
char **path1, char **path2,
- struct node **wnode1, struct node **wnode2,
- int ticket)
+ struct node **wnode1, struct node **wnode2)
{
int err;
/* FIXME: locking two paths needs deadlock checking */
- err = try_get_path(f, nodeid1, name1, path1, wnode1, ticket);
+ err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
if (!err) {
- err = try_get_path(f, nodeid2, name2, path2, wnode2, ticket);
- if (err)
- unlock_path(f, nodeid1, wnode1 ? *wnode1 : NULL, NULL,
- ticket);
+ err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+ if (err) {
+ struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
+ unlock_path(f, nodeid1, wn1, NULL);
+ free(*path1);
+ }
}
return err;
}
@@ -729,27 +1225,27 @@
struct node **wnode1, struct node **wnode2)
{
int err;
- int ticket;
pthread_mutex_lock(&f->lock);
- ticket = get_ticket(f);
err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
- path1, path2, wnode1, wnode2, ticket);
+ path1, path2, wnode1, wnode2);
if (err == -EAGAIN) {
- struct lock_queue_element qe;
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid1,
+ .name1 = name1,
+ .path1 = path1,
+ .wnode1 = wnode1,
+ .nodeid2 = nodeid2,
+ .name2 = name2,
+ .path2 = path2,
+ .wnode2 = wnode2,
+ };
- queue_path(f, &qe, nodeid1, name1, !!wnode1);
- debug_path(f, " path2", nodeid2, name2, !!wnode2);
- do {
- wait_on_path(f, &qe, nodeid1, name1, !!wnode1);
- debug_path(f, " path2", nodeid2, name2, !!wnode2);
- err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
- path1, path2, wnode1, wnode2,
- ticket);
- wake_up_next(&qe);
- } while (err == -EAGAIN);
- dequeue_path(f, &qe, nodeid1, name1, !!wnode1);
- debug_path(f, " path2", nodeid2, name2, !!wnode2);
+ debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+ debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+ err = wait_path(f, &qe);
+ debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+ debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
}
pthread_mutex_unlock(&f->lock);
@@ -760,8 +1256,9 @@
struct node *wnode, char *path)
{
pthread_mutex_lock(&f->lock);
- unlock_path(f, nodeid, wnode, NULL, 0);
- wake_up_first(f);
+ unlock_path(f, nodeid, wnode, NULL);
+ if (f->lockq)
+ wake_up_queued(f);
pthread_mutex_unlock(&f->lock);
free(path);
}
@@ -777,9 +1274,9 @@
char *path1, char *path2)
{
pthread_mutex_lock(&f->lock);
- unlock_path(f, nodeid1, wnode1, NULL, 0);
- unlock_path(f, nodeid2, wnode2, NULL, 0);
- wake_up_first(f);
+ unlock_path(f, nodeid1, wnode1, NULL);
+ unlock_path(f, nodeid2, wnode2, NULL);
+ wake_up_queued(f);
pthread_mutex_unlock(&f->lock);
free(path1);
free(path2);
@@ -798,29 +1295,34 @@
* create and opendir
*/
while (node->nlookup == nlookup && node->treelock) {
- struct lock_queue_element qe;
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid,
+ };
- queue_path(f, &qe, node->nodeid, NULL, 0);
+ debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+ queue_path(f, &qe);
+
do {
- wait_on_path(f, &qe, node->nodeid, NULL, 0);
- wake_up_next(&qe);
-
+ pthread_cond_wait(&qe.cond, &f->lock);
} while (node->nlookup == nlookup && node->treelock);
- dequeue_path(f, &qe, node->nodeid, NULL, 0);
+
+ dequeue_path(f, &qe);
+ debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
}
assert(node->nlookup >= nlookup);
node->nlookup -= nlookup;
if (!node->nlookup) {
- unhash_name(f, node);
unref_node(f, node);
+ } else if (lru_enabled(f) && node->nlookup == 1) {
+ set_forget_time(f, node);
}
pthread_mutex_unlock(&f->lock);
}
static void unlink_node(struct fuse *f, struct node *node)
{
- if (f->conf.noforget) {
+ if (f->conf.remember) {
assert(node->nlookup > 1);
node->nlookup--;
}
@@ -959,7 +1461,7 @@
fuse_do_prepare_interrupt(req, d);
}
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
static int fuse_compat_open(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi)
@@ -1054,7 +1556,7 @@
return err;
}
-#else /* __FreeBSD__ */
+#else /* __FreeBSD__ || __NetBSD__ */
static inline int fuse_compat_open(struct fuse_fs *fs, char *path,
struct fuse_file_info *fi)
@@ -1080,7 +1582,7 @@
return fs->op.statfs(fs->compat == 25 ? "/" : path, buf);
}
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf)
{
@@ -1243,52 +1745,140 @@
}
}
-int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
- off64_t off, struct fuse_file_info *fi)
+static void fuse_free_buf(struct fuse_bufvec *buf)
+{
+ if (buf != NULL) {
+ size_t i;
+
+ for (i = 0; i < buf->count; i++)
+ free(buf->buf[i].mem);
+ free(buf);
+ }
+}
+
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec **bufp, size_t size, off64_t off,
+ struct fuse_file_info *fi)
{
fuse_get_context()->private_data = fs->user_data;
- if (fs->op.read) {
+ if (fs->op.read || fs->op.read_buf) {
int res;
if (fs->debug)
fprintf(stderr,
- "read[%llu] %lu bytes from %llu flags: 0x%x\n",
+ "read[%llu] %zu bytes from %llu flags: 0x%x\n",
(unsigned long long) fi->fh,
- (unsigned long) size, (unsigned long long) off,
- fi->flags);
+ size, (unsigned long long) off, fi->flags);
- res = fs->op.read(path, buf, size, off, fi);
+ if (fs->op.read_buf) {
+ res = fs->op.read_buf(path, bufp, size, off, fi);
+ } else {
+ struct fuse_bufvec *buf;
+ void *mem;
+
+ buf = malloc(sizeof(struct fuse_bufvec));
+ if (buf == NULL)
+ return -ENOMEM;
+
+ mem = malloc(size);
+ if (mem == NULL) {
+ free(buf);
+ return -ENOMEM;
+ }
+ *buf = FUSE_BUFVEC_INIT(size);
+ buf->buf[0].mem = mem;
+ *bufp = buf;
+
+ res = fs->op.read(path, mem, size, off, fi);
+ if (res >= 0)
+ buf->buf[0].size = res;
+ }
if (fs->debug && res >= 0)
- fprintf(stderr, " read[%llu] %u bytes from %llu\n",
- (unsigned long long) fi->fh, res,
+ fprintf(stderr, " read[%llu] %zu bytes from %llu\n",
+ (unsigned long long) fi->fh,
+ fuse_buf_size(*bufp),
(unsigned long long) off);
- if (res > (int) size)
+ if (res >= 0 && fuse_buf_size(*bufp) > (int) size)
fprintf(stderr, "fuse: read too many bytes\n");
- return res;
+ if (res < 0)
+ return res;
+
+ return 0;
} else {
return -ENOSYS;
}
}
-int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
- size_t size, off64_t off, struct fuse_file_info *fi)
+int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+ off64_t off, struct fuse_file_info *fi)
+{
+ int res;
+ struct fuse_bufvec *buf = NULL;
+
+ res = fuse_fs_read_buf(fs, path, &buf, size, off, fi);
+ if (res == 0) {
+ struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
+ dst.buf[0].mem = mem;
+ res = fuse_buf_copy(&dst, buf, 0);
+ }
+ fuse_free_buf(buf);
+
+ return res;
+}
+
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec *buf, off64_t off,
+ struct fuse_file_info *fi)
{
fuse_get_context()->private_data = fs->user_data;
- if (fs->op.write) {
+ if (fs->op.write_buf || fs->op.write) {
int res;
+ size_t size = fuse_buf_size(buf);
+ assert(buf->idx == 0 && buf->off == 0);
if (fs->debug)
fprintf(stderr,
- "write%s[%llu] %lu bytes to %llu flags: 0x%x\n",
+ "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
fi->writepage ? "page" : "",
(unsigned long long) fi->fh,
- (unsigned long) size, (unsigned long long) off,
+ size,
+ (unsigned long long) off,
fi->flags);
- res = fs->op.write(path, buf, size, off, fi);
+ if (fs->op.write_buf) {
+ res = fs->op.write_buf(path, buf, off, fi);
+ } else {
+ void *mem = NULL;
+ struct fuse_buf *flatbuf;
+ struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+ if (buf->count == 1 &&
+ !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+ flatbuf = &buf->buf[0];
+ } else {
+ res = -ENOMEM;
+ mem = malloc(size);
+ if (mem == NULL)
+ goto out;
+
+ tmp.buf[0].mem = mem;
+ res = fuse_buf_copy(&tmp, buf, 0);
+ if (res <= 0)
+ goto out_free;
+
+ tmp.buf[0].size = res;
+ flatbuf = &tmp.buf[0];
+ }
+
+ res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+ off, fi);
+out_free:
+ free(mem);
+ }
+out:
if (fs->debug && res >= 0)
fprintf(stderr, " write%s[%llu] %u bytes to %llu\n",
fi->writepage ? "page" : "",
@@ -1303,6 +1893,16 @@
}
}
+int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+ size_t size, off64_t off, struct fuse_file_info *fi)
+{
+ struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
+ bufv.buf[0].mem = (void *) mem;
+
+ return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+}
+
int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
struct fuse_file_info *fi)
{
@@ -1469,6 +2069,27 @@
}
}
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int op)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.flock) {
+ if (fs->debug) {
+ int xop = op & ~LOCK_NB;
+
+ fprintf(stderr, "lock[%llu] %s%s\n",
+ (unsigned long long) fi->fh,
+ xop == LOCK_SH ? "LOCK_SH" :
+ (xop == LOCK_EX ? "LOCK_EX" :
+ (xop == LOCK_UN ? "LOCK_UN" : "???")),
+ (op & LOCK_NB) ? "|LOCK_NB" : "");
+ }
+ return fs->op.flock(path, fi, op);
+ } else {
+ return -ENOSYS;
+ }
+}
+
int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid)
{
fuse_get_context()->private_data = fs->user_data;
@@ -1503,8 +2124,8 @@
fuse_get_context()->private_data = fs->user_data;
if (fs->op.ftruncate) {
if (fs->debug)
- fprintf(stderr, "ftruncate[%llu] %s %llu\n",
- (unsigned long long) fi->fh, path,
+ fprintf(stderr, "ftruncate[%llu] %llu\n",
+ (unsigned long long) fi->fh,
(unsigned long long) size);
return fs->op.ftruncate(path, size, fi);
@@ -1714,6 +2335,23 @@
return -ENOSYS;
}
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+ off64_t offset, off64_t length, struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.fallocate) {
+ if (fs->debug)
+ fprintf(stderr, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+ path,
+ mode,
+ (unsigned long long) offset,
+ (unsigned long long) length);
+
+ return fs->op.fallocate(path, mode, offset, length, fi);
+ } else
+ return -ENOSYS;
+}
+
static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
{
struct node *node;
@@ -1750,10 +2388,9 @@
newnode = lookup_node(f, dir, newname);
} while(newnode);
- try_get_path(f, dir, newname, &newpath, NULL, 0);
+ res = try_get_path(f, dir, newname, &newpath, NULL, false);
pthread_mutex_unlock(&f->lock);
-
- if (!newpath)
+ if (res)
break;
res = fuse_fs_getattr(f->fs, newpath, &buf);
@@ -1861,7 +2498,7 @@
c = (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
if (c == NULL) {
c = (struct fuse_context_i *)
- malloc(sizeof(struct fuse_context_i));
+ calloc(1, sizeof(struct fuse_context_i));
if (c == NULL) {
/* This is hard to deal with properly, so just
abort. If memory is so low that the
@@ -1945,6 +2582,12 @@
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn)
{
fuse_get_context()->private_data = fs->user_data;
+ if (!fs->op.write_buf)
+ conn->want &= ~FUSE_CAP_SPLICE_READ;
+ if (!fs->op.lock)
+ conn->want &= ~FUSE_CAP_POSIX_LOCKS;
+ if (!fs->op.flock)
+ conn->want &= ~FUSE_CAP_FLOCK_LOCKS;
if (fs->op.init)
fs->user_data = fs->op.init(conn);
}
@@ -2038,17 +2681,34 @@
reply_entry(req, &e, err);
}
+static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+{
+ if (f->conf.debug)
+ fprintf(stderr, "FORGET %llu/%llu\n", (unsigned long long)ino,
+ (unsigned long long) nlookup);
+ forget_node(f, ino, nlookup);
+}
+
static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino,
unsigned long nlookup)
{
- struct fuse *f = req_fuse(req);
- if (f->conf.debug)
- fprintf(stderr, "FORGET %llu/%lu\n", (unsigned long long)ino,
- nlookup);
- forget_node(f, ino, nlookup);
+ do_forget(req_fuse(req), ino, nlookup);
fuse_reply_none(req);
}
+static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+ struct fuse_forget_data *forgets)
+{
+ struct fuse *f = req_fuse(req);
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
+ fuse_reply_none(req);
+}
+
+
static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
struct fuse_file_info *fi)
{
@@ -2059,7 +2719,7 @@
memset(&buf, 0, sizeof(buf));
- if (fi != NULL)
+ if (fi != NULL && f->fs->op.fgetattr)
err = get_path_nullok(f, ino, &path);
else
err = get_path(f, ino, &path);
@@ -2074,11 +2734,15 @@
free_path(f, ino, path);
}
if (!err) {
- if (f->conf.auto_cache) {
- pthread_mutex_lock(&f->lock);
- update_stat(get_node(f, ino), &buf);
- pthread_mutex_unlock(&f->lock);
- }
+ struct node *node;
+
+ pthread_mutex_lock(&f->lock);
+ node = get_node(f, ino);
+ if (node->is_hidden && buf.st_nlink > 0)
+ buf.st_nlink--;
+ if (f->conf.auto_cache)
+ update_stat(node, &buf);
+ pthread_mutex_unlock(&f->lock);
set_stat(f, ino, &buf);
fuse_reply_attr(req, &buf, f->conf.attr_timeout);
} else
@@ -2102,7 +2766,11 @@
char *path;
int err;
- err = get_path(f, ino, &path);
+ if (valid == FUSE_SET_ATTR_SIZE && fi != NULL &&
+ f->fs->op.ftruncate && f->fs->op.fgetattr)
+ err = get_path_nullok(f, ino, &path);
+ else
+ err = get_path(f, ino, &path);
if (!err) {
struct fuse_intr_data d;
fuse_prepare_interrupt(f, req, &d);
@@ -2124,6 +2792,29 @@
err = fuse_fs_truncate(f->fs, path,
attr->st_size);
}
+#ifdef HAVE_UTIMENSAT
+ if (!err && f->utime_omit_ok &&
+ (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+ struct timespec tv[2];
+
+ tv[0].tv_sec = 0;
+ tv[1].tv_sec = 0;
+ tv[0].tv_nsec = UTIME_OMIT;
+ tv[1].tv_nsec = UTIME_OMIT;
+
+ if (valid & FUSE_SET_ATTR_ATIME_NOW)
+ tv[0].tv_nsec = UTIME_NOW;
+ else if (valid & FUSE_SET_ATTR_ATIME)
+ tv[0] = attr->st_atim;
+
+ if (valid & FUSE_SET_ATTR_MTIME_NOW)
+ tv[1].tv_nsec = UTIME_NOW;
+ else if (valid & FUSE_SET_ATTR_MTIME)
+ tv[1] = attr->st_mtim;
+
+ err = fuse_fs_utimens(f->fs, path, tv);
+ } else
+#endif
if (!err &&
(valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
(FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
@@ -2134,8 +2825,12 @@
tv[1].tv_nsec = ST_MTIM_NSEC(attr);
err = fuse_fs_utimens(f->fs, path, tv);
}
- if (!err)
- err = fuse_fs_getattr(f->fs, path, &buf);
+ if (!err) {
+ if (fi)
+ err = fuse_fs_fgetattr(f->fs, path, &buf, fi);
+ else
+ err = fuse_fs_getattr(f->fs, path, &buf);
+ }
fuse_finish_interrupt(f, req, &d);
free_path(f, ino, path);
}
@@ -2381,8 +3076,14 @@
{
struct node *node;
int unlink_hidden = 0;
+ const char *compatpath;
- fuse_fs_release(f->fs, (path || f->nullpath_ok) ? path : "-", fi);
+ if (path != NULL || f->nullpath_ok || f->conf.nopath)
+ compatpath = path;
+ else
+ compatpath = "-";
+
+ fuse_fs_release(f->fs, compatpath, fi);
pthread_mutex_lock(&f->lock);
node = get_node(f, ino);
@@ -2394,8 +3095,18 @@
}
pthread_mutex_unlock(&f->lock);
- if(unlink_hidden && path)
- fuse_fs_unlink(f->fs, path);
+ if(unlink_hidden) {
+ if (path) {
+ fuse_fs_unlink(f->fs, path);
+ } else if (f->conf.nopath) {
+ char *unlinkpath;
+
+ if (get_path(f, ino, &unlinkpath) == 0)
+ fuse_fs_unlink(f->fs, unlinkpath);
+
+ free_path(f, ino, unlinkpath);
+ }
+ }
}
static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
@@ -2437,9 +3148,7 @@
if (fuse_reply_create(req, &e, fi) == -ENOENT) {
/* The open syscall was interrupted, so it
must be cancelled */
- fuse_prepare_interrupt(f, req, &d);
fuse_do_release(f, e.ino, path, fi);
- fuse_finish_interrupt(f, req, &d);
forget_node(f, e.ino, 1);
}
} else {
@@ -2517,9 +3226,7 @@
if (fuse_reply_open(req, fi) == -ENOENT) {
/* The open syscall was interrupted, so it
must be cancelled */
- fuse_prepare_interrupt(f, req, &d);
fuse_do_release(f, ino, path, fi);
- fuse_finish_interrupt(f, req, &d);
}
} else
reply_err(req, err);
@@ -2531,36 +3238,31 @@
off64_t off, struct fuse_file_info *fi)
{
struct fuse *f = req_fuse_prepare(req);
+ struct fuse_bufvec *buf = NULL;
char *path;
- char *buf;
int res;
- buf = (char *) malloc(size);
- if (buf == NULL) {
- reply_err(req, -ENOMEM);
- return;
- }
-
res = get_path_nullok(f, ino, &path);
if (res == 0) {
struct fuse_intr_data d;
fuse_prepare_interrupt(f, req, &d);
- res = fuse_fs_read(f->fs, path, buf, size, off, fi);
+ res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
fuse_finish_interrupt(f, req, &d);
free_path(f, ino, path);
}
- if (res >= 0)
- fuse_reply_buf(req, buf, res);
+ if (res == 0)
+ fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE);
else
reply_err(req, res);
- free(buf);
+ fuse_free_buf(buf);
}
-static void fuse_lib_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
- size_t size, off64_t off, struct fuse_file_info *fi)
+static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_bufvec *buf, off64_t off,
+ struct fuse_file_info *fi)
{
struct fuse *f = req_fuse_prepare(req);
char *path;
@@ -2571,8 +3273,7 @@
struct fuse_intr_data d;
fuse_prepare_interrupt(f, req, &d);
- res = fuse_fs_write(f->fs, path, buf, size, off, fi);
- printf("died\n");
+ res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
fuse_finish_interrupt(f, req, &d);
free_path(f, ino, path);
}
@@ -2651,9 +3352,7 @@
if (fuse_reply_open(req, llfi) == -ENOENT) {
/* The opendir syscall was interrupted, so it
must be cancelled */
- fuse_prepare_interrupt(f, req, &d);
fuse_fs_releasedir(f->fs, path, &fi);
- fuse_finish_interrupt(f, req, &d);
pthread_mutex_destroy(&dh->lock);
free(dh);
}
@@ -2747,7 +3446,10 @@
char *path;
int err;
- err = get_path(f, ino, &path);
+ if (f->fs->op.readdir)
+ err = get_path_nullok(f, ino, &path);
+ else
+ err = get_path(f, ino, &path);
if (!err) {
struct fuse_intr_data d;
@@ -2812,10 +3514,16 @@
struct fuse_file_info fi;
struct fuse_dh *dh = get_dirhandle(llfi, &fi);
char *path;
+ const char *compatpath;
- get_path(f, ino, &path);
+ get_path_nullok(f, ino, &path);
+ if (path != NULL || f->nullpath_ok || f->conf.nopath)
+ compatpath = path;
+ else
+ compatpath = "-";
+
fuse_prepare_interrupt(f, req, &d);
- fuse_fs_releasedir(f->fs, (path || f->nullpath_ok) ? path : "-", &fi);
+ fuse_fs_releasedir(f->fs, compatpath, &fi);
fuse_finish_interrupt(f, req, &d);
free_path(f, ino, path);
@@ -2837,7 +3545,7 @@
get_dirhandle(llfi, &fi);
- err = get_path(f, ino, &path);
+ err = get_path_nullok(f, ino, &path);
if (!err) {
struct fuse_intr_data d;
fuse_prepare_interrupt(f, req, &d);
@@ -3155,7 +3863,7 @@
char *path;
int err = 0;
- get_path(f, ino, &path);
+ get_path_nullok(f, ino, &path);
if (fi->flush) {
err = fuse_flush_common(f, req, ino, path, fi);
if (err == -ENOSYS)
@@ -3177,7 +3885,7 @@
char *path;
int err;
- get_path(f, ino, &path);
+ get_path_nullok(f, ino, &path);
err = fuse_flush_common(f, req, ino, path, fi);
free_path(f, ino, path);
@@ -3247,6 +3955,24 @@
reply_err(req, err);
}
+static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, int op)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (err == 0) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_flock(f->fs, path, fi, op);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
uint64_t idx)
{
@@ -3293,7 +4019,7 @@
if (out_buf)
memcpy(out_buf, in_buf, in_bufsz);
- err = get_path(f, ino, &path);
+ err = get_path_nullok(f, ino, &path);
if (err)
goto err;
@@ -3319,20 +4045,93 @@
struct fuse *f = req_fuse_prepare(req);
struct fuse_intr_data d;
char *path;
- int ret;
+ int err;
unsigned revents = 0;
- ret = get_path(f, ino, &path);
- if (!ret) {
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
fuse_prepare_interrupt(f, req, &d);
- ret = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+ err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
fuse_finish_interrupt(f, req, &d);
free_path(f, ino, path);
}
- if (!ret)
+ if (!err)
fuse_reply_poll(req, revents);
else
- reply_err(req, ret);
+ reply_err(req, err);
+}
+
+static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+ off64_t offset, off64_t length, struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static int clean_delay(struct fuse *f)
+{
+ /*
+ * This is calculating the delay between clean runs. To
+ * reduce the number of cleans we are doing them 10 times
+ * within the remember window.
+ */
+ int min_sleep = 60;
+ int max_sleep = 3600;
+ int sleep_time = f->conf.remember / 10;
+
+ if (sleep_time > max_sleep)
+ return max_sleep;
+ if (sleep_time < min_sleep)
+ return min_sleep;
+ return sleep_time;
+}
+
+int fuse_clean_cache(struct fuse *f)
+{
+ struct node_lru *lnode;
+ struct list_head *curr, *next;
+ struct node *node;
+ struct timespec now;
+
+ pthread_mutex_lock(&f->lock);
+
+ curr_time(&now);
+
+ for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+ double age;
+
+ next = curr->next;
+ lnode = list_entry(curr, struct node_lru, lru);
+ node = &lnode->node;
+
+ age = diff_timespec(&now, &lnode->forget_time);
+ if (age <= f->conf.remember)
+ break;
+
+ assert(node->nlookup == 1);
+
+ /* Don't forget active directories */
+ if (node->refctr > 1)
+ continue;
+
+ node->nlookup = 0;
+ unhash_name(f, node);
+ unref_node(f, node);
+ }
+ pthread_mutex_unlock(&f->lock);
+
+ return clean_delay(f);
}
static struct fuse_lowlevel_ops fuse_path_ops = {
@@ -3340,6 +4139,7 @@
.destroy = fuse_lib_destroy,
.lookup = fuse_lib_lookup,
.forget = fuse_lib_forget,
+ .forget_multi = fuse_lib_forget_multi,
.getattr = fuse_lib_getattr,
.setattr = fuse_lib_setattr,
.access = fuse_lib_access,
@@ -3354,7 +4154,7 @@
.create = fuse_lib_create,
.open = fuse_lib_open,
.read = fuse_lib_read,
- .write = fuse_lib_write,
+ .write_buf = fuse_lib_write_buf,
.flush = fuse_lib_flush,
.release = fuse_lib_release,
.fsync = fuse_lib_fsync,
@@ -3369,9 +4169,11 @@
.removexattr = fuse_lib_removexattr,
.getlk = fuse_lib_getlk,
.setlk = fuse_lib_setlk,
+ .flock = fuse_lib_flock,
.bmap = fuse_lib_bmap,
.ioctl = fuse_lib_ioctl,
.poll = fuse_lib_poll,
+ .fallocate = fuse_lib_fallocate,
};
int fuse_notify_poll(struct fuse_pollhandle *ph)
@@ -3436,12 +4238,77 @@
return cmd;
}
+static int fuse_session_loop_remember(struct fuse *f)
+{
+ struct fuse_session *se = f->se;
+ int res = 0;
+ struct timespec now;
+ time_t next_clean;
+ struct fuse_chan *ch = fuse_session_next_chan(se, NULL);
+ size_t bufsize = fuse_chan_bufsize(ch);
+ char *buf = (char *) malloc(bufsize);
+ struct pollfd fds = {
+ .fd = fuse_chan_fd(ch),
+ .events = POLLIN
+ };
+
+ if (!buf) {
+ fprintf(stderr, "fuse: failed to allocate read buffer\n");
+ return -1;
+ }
+
+ curr_time(&now);
+ next_clean = now.tv_sec;
+ while (!fuse_session_exited(se)) {
+ struct fuse_chan *tmpch = ch;
+ struct fuse_buf fbuf = {
+ .mem = buf,
+ .size = bufsize,
+ };
+ unsigned timeout;
+
+ curr_time(&now);
+ if (now.tv_sec < next_clean)
+ timeout = next_clean - now.tv_sec;
+ else
+ timeout = 0;
+
+ res = poll(&fds, 1, timeout * 1000);
+ if (res == -1) {
+ if (errno == -EINTR)
+ continue;
+ else
+ break;
+ } else if (res > 0) {
+ res = fuse_session_receive_buf(se, &fbuf, &tmpch);
+
+ if (res == -EINTR)
+ continue;
+ if (res <= 0)
+ break;
+
+ fuse_session_process_buf(se, &fbuf, tmpch);
+ } else {
+ timeout = fuse_clean_cache(f);
+ curr_time(&now);
+ next_clean = now.tv_sec + timeout;
+ }
+ }
+
+ free(buf);
+ fuse_session_reset(se);
+ return res < 0 ? -1 : 0;
+}
+
int fuse_loop(struct fuse *f)
{
- if (f)
- return fuse_session_loop(f->se);
- else
+ if (!f)
return -1;
+
+ if (lru_enabled(f))
+ return fuse_session_loop_remember(f);
+
+ return fuse_session_loop(f->se);
}
int fuse_invalidate(struct fuse *f, const char *path)
@@ -3521,7 +4388,9 @@
FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
- FUSE_LIB_OPT("noforget", noforget, 1),
+ FUSE_LIB_OPT("noforget", remember, -1),
+ FUSE_LIB_OPT("remember=%u", remember, 0),
+ FUSE_LIB_OPT("nopath", nopath, 1),
FUSE_LIB_OPT("intr", intr, 1),
FUSE_LIB_OPT("intr_signal=%d", intr_signal, 0),
FUSE_LIB_OPT("modules=%s", modules, 0),
@@ -3544,6 +4413,8 @@
" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+" -o noforget never forget cached inodes\n"
+" -o remember=T remember cached inodes for T seconds (0s)\n"
" -o intr allow requests to be interrupted\n"
" -o intr_signal=NUM signal to send on interrupt (%i)\n"
" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n"
@@ -3643,6 +4514,8 @@
newfs->m = m;
f->fs = newfs;
f->nullpath_ok = newfs->op.flag_nullpath_ok && f->nullpath_ok;
+ f->conf.nopath = newfs->op.flag_nopath && f->conf.nopath;
+ f->utime_omit_ok = newfs->op.flag_utime_omit_ok && f->utime_omit_ok;
return 0;
}
@@ -3668,6 +4541,50 @@
return fs;
}
+static int node_table_init(struct node_table *t)
+{
+ t->size = NODE_TABLE_MIN_SIZE;
+ t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+ if (t->array == NULL) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+ }
+ t->use = 0;
+ t->split = 0;
+
+ return 0;
+}
+
+static void *fuse_prune_nodes(void *fuse)
+{
+ struct fuse *f = fuse;
+ int sleep_time;
+
+ while(1) {
+ sleep_time = fuse_clean_cache(f);
+ sleep(sleep_time);
+ }
+ return NULL;
+}
+
+int fuse_start_cleanup_thread(struct fuse *f)
+{
+ if (lru_enabled(f))
+ return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
+ return 0;
+}
+
+void fuse_stop_cleanup_thread(struct fuse *f)
+{
+ if (lru_enabled(f)) {
+ pthread_mutex_lock(&f->lock);
+ pthread_cancel(f->prune_thread);
+ pthread_mutex_unlock(&f->lock);
+ pthread_join(f->prune_thread, NULL);
+ }
+}
+
struct fuse *fuse_new_common(struct fuse_chan *ch, struct fuse_args *args,
const struct fuse_operations *op,
size_t op_size, void *user_data, int compat)
@@ -3693,6 +4610,8 @@
fs->compat = compat;
f->fs = fs;
f->nullpath_ok = fs->op.flag_nullpath_ok;
+ f->conf.nopath = fs->op.flag_nopath;
+ f->utime_omit_ok = fs->op.flag_utime_omit_ok;
/* Oh f**k, this is ugly! */
if (!fs->op.lock) {
@@ -3705,6 +4624,11 @@
f->conf.negative_timeout = 0.0;
f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+ f->pagesize = getpagesize();
+ init_list_head(&f->partial_slabs);
+ init_list_head(&f->full_slabs);
+ init_list_head(&f->lru_table);
+
if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
fuse_lib_opt_proc) == -1)
goto out_free_fs;
@@ -3727,7 +4651,7 @@
if (!f->conf.ac_attr_timeout_set)
f->conf.ac_attr_timeout = f->conf.attr_timeout;
-#ifdef __FreeBSD__
+#if defined(__FreeBSD__) || defined(__NetBSD__)
/*
* In FreeBSD, we always use these settings as inode numbers
* are needed to make getcwd(3) work.
@@ -3749,66 +4673,51 @@
fuse_session_add_chan(f->se, ch);
- if (f->conf.debug)
+ if (f->conf.debug) {
fprintf(stderr, "nullpath_ok: %i\n", f->nullpath_ok);
+ fprintf(stderr, "nopath: %i\n", f->conf.nopath);
+ fprintf(stderr, "utime_omit_ok: %i\n", f->utime_omit_ok);
+ }
/* Trace topmost layer by default */
f->fs->debug = f->conf.debug;
f->ctr = 0;
f->generation = 0;
- /* FIXME: Dynamic hash table */
- f->name_table_size = 14057;
- f->name_table = (struct node **)
- calloc(1, sizeof(struct node *) * f->name_table_size);
- if (f->name_table == NULL) {
- fprintf(stderr, "fuse: memory allocation failed\n");
+ if (node_table_init(&f->name_table) == -1)
goto out_free_session;
- }
- f->id_table_size = 14057;
- f->id_table = (struct node **)
- calloc(1, sizeof(struct node *) * f->id_table_size);
- if (f->id_table == NULL) {
- fprintf(stderr, "fuse: memory allocation failed\n");
+ if (node_table_init(&f->id_table) == -1)
goto out_free_name_table;
- }
fuse_mutex_init(&f->lock);
- root = (struct node *) calloc(1, sizeof(struct node));
+ root = alloc_node(f);
if (root == NULL) {
fprintf(stderr, "fuse: memory allocation failed\n");
goto out_free_id_table;
}
- root->name = strdup("/");
- if (root->name == NULL) {
- fprintf(stderr, "fuse: memory allocation failed\n");
- goto out_free_root;
- }
+ strcpy(root->inline_name, "/");
+ root->name = root->inline_name;
if (f->conf.intr &&
fuse_init_intr_signal(f->conf.intr_signal,
&f->intr_installed) == -1)
- goto out_free_root_name;
+ goto out_free_root;
root->parent = NULL;
root->nodeid = FUSE_ROOT_ID;
- root->generation = 0;
- root->refctr = 1;
- root->nlookup = 1;
+ inc_nlookup(root);
hash_id(f, root);
return f;
-out_free_root_name:
- free(root->name);
out_free_root:
free(root);
out_free_id_table:
- free(f->id_table);
+ free(f->id_table.array);
out_free_name_table:
- free(f->name_table);
+ free(f->name_table.array);
out_free_session:
fuse_session_destroy(f->se);
out_free_fs:
@@ -3845,14 +4754,14 @@
memset(c, 0, sizeof(*c));
c->ctx.fuse = f;
- for (i = 0; i < f->id_table_size; i++) {
+ for (i = 0; i < f->id_table.size; i++) {
struct node *node;
- for (node = f->id_table[i]; node != NULL;
+ for (node = f->id_table.array[i]; node != NULL;
node = node->id_next) {
if (node->is_hidden) {
char *path;
- if (try_get_path(f, node->nodeid, NULL, &path, NULL, 0) == 0) {
+ if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
fuse_fs_unlink(f->fs, path);
free(path);
}
@@ -3860,17 +4769,21 @@
}
}
}
- for (i = 0; i < f->id_table_size; i++) {
+ for (i = 0; i < f->id_table.size; i++) {
struct node *node;
struct node *next;
- for (node = f->id_table[i]; node != NULL; node = next) {
+ for (node = f->id_table.array[i]; node != NULL; node = next) {
next = node->id_next;
- free_node(node);
+ free_node(f, node);
+ f->id_table.use--;
}
}
- free(f->id_table);
- free(f->name_table);
+ assert(list_empty(&f->partial_slabs));
+ assert(list_empty(&f->full_slabs));
+
+ free(f->id_table.array);
+ free(f->name_table.array);
pthread_mutex_destroy(&f->lock);
fuse_session_destroy(f->se);
free(f->conf.modules);
@@ -3903,7 +4816,7 @@
fuse_modules = mod;
}
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
static struct fuse *fuse_new_common_compat(int fd, const char *opts,
const struct fuse_operations *op,
@@ -3960,7 +4873,7 @@
FUSE_SYMVER(".symver fuse_new_compat2,fuse_new@");
FUSE_SYMVER(".symver fuse_new_compat22,fuse_new@FUSE_2.2");
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
struct fuse *fuse_new_compat25(int fd, struct fuse_args *args,
const struct fuse_operations_compat25 *op,
diff --git a/fuse/fuse_i.h b/fuse/fuse_i.h
index 6285c95..78f1467 100644
--- a/fuse/fuse_i.h
+++ b/fuse/fuse_i.h
@@ -8,7 +8,6 @@
#include "fuse.h"
#include "fuse_lowlevel.h"
-#include <pthread.h>
struct fuse_chan;
struct fuse_ll;
@@ -16,6 +15,12 @@
struct fuse_session {
struct fuse_session_ops op;
+ int (*receive_buf)(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan **chp);
+
+ void (*process_buf)(void *data, const struct fuse_buf *buf,
+ struct fuse_chan *ch);
+
void *data;
volatile int exited;
@@ -31,6 +36,7 @@
struct fuse_ctx ctx;
struct fuse_chan *ch;
int interrupted;
+ unsigned int ioctl_64bit : 1;
union {
struct {
uint64_t unique;
@@ -44,12 +50,27 @@
struct fuse_req *prev;
};
+struct fuse_notify_req {
+ uint64_t unique;
+ void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+ const void *, const struct fuse_buf *);
+ struct fuse_notify_req *next;
+ struct fuse_notify_req *prev;
+};
+
struct fuse_ll {
int debug;
int allow_root;
int atomic_o_trunc;
- int no_remote_lock;
+ int no_remote_posix_lock;
+ int no_remote_flock;
int big_writes;
+ int splice_write;
+ int splice_move;
+ int splice_read;
+ int no_splice_write;
+ int no_splice_move;
+ int no_splice_read;
struct fuse_lowlevel_ops op;
int got_init;
struct cuse_data *cuse_data;
@@ -60,6 +81,10 @@
struct fuse_req interrupts;
pthread_mutex_t lock;
int got_destroy;
+ pthread_key_t pipe_key;
+ int broken_splice_nonblock;
+ uint64_t notify_ctr;
+ struct fuse_notify_req notify_list;
};
struct fuse_cmd {
@@ -99,3 +124,5 @@
int compat);
void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
diff --git a/fuse/fuse_kern_chan.c b/fuse/fuse_kern_chan.c
index 03291c3..5f77bbf 100644
--- a/fuse/fuse_kern_chan.c
+++ b/fuse/fuse_kern_chan.c
@@ -40,7 +40,7 @@
fuse_session_exit(se);
return 0;
}
- /* Errors occuring during normal operation: EINTR (read
+ /* Errors occurring during normal operation: EINTR (read
interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
umounted) */
if (err != EINTR && err != EAGAIN)
diff --git a/fuse/fuse_loop.c b/fuse/fuse_loop.c
index 104c5d4..b7b4ca4 100644
--- a/fuse/fuse_loop.c
+++ b/fuse/fuse_loop.c
@@ -25,12 +25,19 @@
while (!fuse_session_exited(se)) {
struct fuse_chan *tmpch = ch;
- res = fuse_chan_recv(&tmpch, buf, bufsize);
+ struct fuse_buf fbuf = {
+ .mem = buf,
+ .size = bufsize,
+ };
+
+ res = fuse_session_receive_buf(se, &fbuf, &tmpch);
+
if (res == -EINTR)
continue;
if (res <= 0)
break;
- fuse_session_process(se, buf, res, tmpch);
+
+ fuse_session_process_buf(se, &fbuf, tmpch);
}
free(buf);
diff --git a/fuse/fuse_loop_mt.c b/fuse/fuse_loop_mt.c
index a73c399..7e400c2 100644
--- a/fuse/fuse_loop_mt.c
+++ b/fuse/fuse_loop_mt.c
@@ -9,6 +9,7 @@
#include "fuse_lowlevel.h"
#include "fuse_misc.h"
#include "fuse_kernel.h"
+#include "fuse_i.h"
#include <stdio.h>
#include <stdlib.h>
@@ -18,8 +19,7 @@
#include <semaphore.h>
#include <errno.h>
#include <sys/time.h>
-
-#ifdef __MULTI_THREAD
+#include <pthread.h>
/* Environment var controlling the thread stack size */
#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
@@ -65,7 +65,7 @@
#define PTHREAD_CANCEL_ENABLE 0
#define PTHREAD_CANCEL_DISABLE 1
-static int fuse_start_thread(struct fuse_mt *mt);
+static int fuse_loop_start_thread(struct fuse_mt *mt);
static void *fuse_do_work(void *data)
{
@@ -75,10 +75,14 @@
while (!fuse_session_exited(mt->se)) {
int isforget = 0;
struct fuse_chan *ch = mt->prevch;
+ struct fuse_buf fbuf = {
+ .mem = w->buf,
+ .size = w->bufsize,
+ };
int res;
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
- res = fuse_chan_recv(&ch, w->buf, w->bufsize);
+ res = fuse_session_receive_buf(mt->se, &fbuf, &ch);
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
if (res == -EINTR)
continue;
@@ -100,16 +104,21 @@
* This disgusting hack is needed so that zillions of threads
* are not created on a burst of FORGET messages
*/
- if (((struct fuse_in_header *) w->buf)->opcode == FUSE_FORGET)
- isforget = 1;
+ if (!(fbuf.flags & FUSE_BUF_IS_FD)) {
+ struct fuse_in_header *in = fbuf.mem;
+
+ if (in->opcode == FUSE_FORGET ||
+ in->opcode == FUSE_BATCH_FORGET)
+ isforget = 1;
+ }
if (!isforget)
mt->numavail--;
if (mt->numavail == 0)
- fuse_start_thread(mt);
+ fuse_loop_start_thread(mt);
pthread_mutex_unlock(&mt->lock);
- fuse_session_process(mt->se, w->buf, res, ch);
+ fuse_session_process_buf(mt->se, &fbuf, ch);
pthread_mutex_lock(&mt->lock);
if (!isforget)
@@ -133,19 +142,46 @@
}
sem_post(&mt->finish);
- pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
- pause();
return NULL;
}
-static int fuse_start_thread(struct fuse_mt *mt)
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
{
sigset_t oldset;
sigset_t newset;
int res;
pthread_attr_t attr;
char *stack_size;
+
+ /* Override default stack size */
+ pthread_attr_init(&attr);
+ stack_size = getenv(ENVNAME_THREAD_STACK);
+ if (stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size)))
+ fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size);
+
+ /* Disallow signal reception in worker threads */
+ sigemptyset(&newset);
+ sigaddset(&newset, SIGTERM);
+ sigaddset(&newset, SIGINT);
+ sigaddset(&newset, SIGHUP);
+ sigaddset(&newset, SIGQUIT);
+ pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+ res = pthread_create(thread_id, &attr, func, arg);
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ pthread_attr_destroy(&attr);
+ if (res != 0) {
+ fprintf(stderr, "fuse: error creating thread: %s\n",
+ strerror(res));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt)
+{
+ int res;
struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
if (!w) {
fprintf(stderr, "fuse: failed to allocate worker structure\n");
@@ -161,25 +197,8 @@
return -1;
}
- /* Override default stack size */
- pthread_attr_init(&attr);
- stack_size = getenv(ENVNAME_THREAD_STACK);
- if (stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size)))
- fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size);
-
- /* Disallow signal reception in worker threads */
- sigemptyset(&newset);
- sigaddset(&newset, SIGTERM);
- sigaddset(&newset, SIGINT);
- sigaddset(&newset, SIGHUP);
- sigaddset(&newset, SIGQUIT);
- pthread_sigmask(SIG_BLOCK, &newset, &oldset);
- res = pthread_create(&w->thread_id, &attr, fuse_do_work, w);
- pthread_sigmask(SIG_SETMASK, &oldset, NULL);
- pthread_attr_destroy(&attr);
- if (res != 0) {
- fprintf(stderr, "fuse: error creating thread: %s\n",
- strerror(res));
+ res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+ if (res == -1) {
free(w->buf);
free(w);
return -1;
@@ -219,7 +238,7 @@
fuse_mutex_init(&mt.lock);
pthread_mutex_lock(&mt.lock);
- err = fuse_start_thread(&mt);
+ err = fuse_loop_start_thread(&mt);
pthread_mutex_unlock(&mt.lock);
if (!err) {
/* sem_wait() is interruptible */
@@ -229,7 +248,6 @@
for (w = mt.main.next; w != &mt.main; w = w->next)
pthread_cancel(w->thread_id);
mt.exit = 1;
- pthread_mutex_unlock(&mt.lock);
while (mt.main.next != &mt.main)
fuse_join_worker(&mt, mt.main.next);
@@ -242,5 +260,3 @@
fuse_session_reset(se);
return err;
}
-
-#endif
diff --git a/fuse/fuse_lowlevel.c b/fuse/fuse_lowlevel.c
index 2e0ad0f..103f831 100644
--- a/fuse/fuse_lowlevel.c
+++ b/fuse/fuse_lowlevel.c
@@ -6,6 +6,9 @@
See the file COPYING.LIB
*/
+#define _GNU_SOURCE
+
+#include "config.h"
#include "fuse_i.h"
#include "fuse_kernel.h"
#include "fuse_opt.h"
@@ -13,7 +16,6 @@
#include "fuse_common_compat.h"
#include "fuse_lowlevel_compat.h"
-#define linux
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
@@ -21,16 +23,36 @@
#include <unistd.h>
#include <limits.h>
#include <errno.h>
+#include <assert.h>
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
#define OFFSET_MAX 0x7fffffffffffffffLL
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
struct fuse_pollhandle {
uint64_t kh;
struct fuse_chan *ch;
struct fuse_ll *f;
};
+static size_t pagesize;
+
+static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+{
+ pagesize = getpagesize();
+}
+
static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
{
attr->ino = stbuf->st_ino;
@@ -116,6 +138,49 @@
destroy_req(req);
}
+static struct fuse_req *fuse_ll_alloc_req(struct fuse_ll *f)
+{
+ struct fuse_req *req;
+
+ req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+ if (req == NULL) {
+ fprintf(stderr, "fuse: failed to allocate request\n");
+ } else {
+ req->f = f;
+ req->ctr = 1;
+ list_init_req(req);
+ fuse_mutex_init(&req->lock);
+ }
+
+ return req;
+}
+
+
+static int fuse_send_msg(struct fuse_ll *f, struct fuse_chan *ch,
+ struct iovec *iov, int count)
+{
+ struct fuse_out_header *out = iov[0].iov_base;
+
+ out->len = iov_length(iov, count);
+ if (f->debug) {
+ if (out->unique == 0) {
+ fprintf(stderr, "NOTIFY: code=%d length=%u\n",
+ out->error, out->len);
+ } else if (out->error) {
+ fprintf(stderr,
+ " unique: %llu, error: %i (%s), outsize: %i\n",
+ (unsigned long long) out->unique, out->error,
+ strerror(-out->error), out->len);
+ } else {
+ fprintf(stderr,
+ " unique: %llu, success, outsize: %i\n",
+ (unsigned long long) out->unique, out->len);
+ }
+ }
+
+ return fuse_chan_send(ch, iov, count);
+}
+
int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
int count)
{
@@ -128,24 +193,11 @@
out.unique = req->unique;
out.error = error;
+
iov[0].iov_base = &out;
iov[0].iov_len = sizeof(struct fuse_out_header);
- out.len = iov_length(iov, count);
- if (req->f->debug) {
- if (out.error) {
- fprintf(stderr,
- " unique: %llu, error: %i (%s), outsize: %i\n",
- (unsigned long long) out.unique, out.error,
- strerror(-out.error), out.len);
- } else {
- fprintf(stderr,
- " unique: %llu, success, outsize: %i\n",
- (unsigned long long) out.unique, out.len);
- }
- }
-
- return fuse_chan_send(req->ch, iov, count);
+ return fuse_send_msg(req->f, req->ch, iov, count);
}
static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
@@ -177,10 +229,8 @@
struct iovec *padded_iov;
padded_iov = malloc((count + 1) * sizeof(struct iovec));
- if (padded_iov == NULL) {
- printf("ENOMEM fuse_reply_iov\n");
- return fuse_reply_err(req, -ENOMEM);
- }
+ if (padded_iov == NULL)
+ return fuse_reply_err(req, ENOMEM);
memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
count++;
@@ -253,7 +303,8 @@
void fuse_reply_none(fuse_req_t req)
{
- fuse_chan_send(req->ch, NULL, 0);
+ if (req->ch)
+ fuse_chan_send(req->ch, NULL, 0);
fuse_free_req(req);
}
@@ -310,10 +361,8 @@
/* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
negative entry */
- if (!e->ino && req->f->conn.proto_minor < 4) {
- printf("ENOENT fuse_reply_entry\n");
+ if (!e->ino && req->f->conn.proto_minor < 4)
return fuse_reply_err(req, ENOENT);
- }
memset(&arg, 0, sizeof(arg));
fill_entry(&arg, e);
@@ -380,6 +429,355 @@
return send_reply_ok(req, buf, size);
}
+static int fuse_send_data_iov_fallback(struct fuse_ll *f, struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf,
+ size_t len)
+{
+ struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+ void *mbuf;
+ int res;
+
+ /* Optimize common case */
+ if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+ !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+ /* FIXME: also avoid memory copy if there are multiple buffers
+ but none of them contain an fd */
+
+ iov[iov_count].iov_base = buf->buf[0].mem;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ return fuse_send_msg(f, ch, iov, iov_count);
+ }
+
+ res = posix_memalign(&mbuf, pagesize, len);
+ if (res != 0)
+ return res;
+
+ mem_buf.buf[0].mem = mbuf;
+ res = fuse_buf_copy(&mem_buf, buf, 0);
+ if (res < 0) {
+ free(mbuf);
+ return -res;
+ }
+ len = res;
+
+ iov[iov_count].iov_base = mbuf;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ res = fuse_send_msg(f, ch, iov, iov_count);
+ free(mbuf);
+
+ return res;
+}
+
+struct fuse_ll_pipe {
+ size_t size;
+ int can_grow;
+ int pipe[2];
+};
+
+static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+{
+ close(llp->pipe[0]);
+ close(llp->pipe[1]);
+ free(llp);
+}
+
+#ifdef HAVE_SPLICE
+static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_ll *f)
+{
+ struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key);
+ if (llp == NULL) {
+ int res;
+
+ llp = malloc(sizeof(struct fuse_ll_pipe));
+ if (llp == NULL)
+ return NULL;
+
+ res = pipe(llp->pipe);
+ if (res == -1) {
+ free(llp);
+ return NULL;
+ }
+
+ if (fcntl(llp->pipe[0], F_SETFL, O_NONBLOCK) == -1 ||
+ fcntl(llp->pipe[1], F_SETFL, O_NONBLOCK) == -1) {
+ close(llp->pipe[0]);
+ close(llp->pipe[1]);
+ free(llp);
+ return NULL;
+ }
+
+ /*
+ *the default size is 16 pages on linux
+ */
+ llp->size = pagesize * 16;
+ llp->can_grow = 1;
+
+ pthread_setspecific(f->pipe_key, llp);
+ }
+
+ return llp;
+}
+#endif
+
+static void fuse_ll_clear_pipe(struct fuse_ll *f)
+{
+ struct fuse_ll_pipe *llp = pthread_getspecific(f->pipe_key);
+ if (llp) {
+ pthread_setspecific(f->pipe_key, NULL);
+ fuse_ll_pipe_free(llp);
+ }
+}
+
+#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+static int read_back(int fd, char *buf, size_t len)
+{
+ int res;
+
+ res = read(fd, buf, len);
+ if (res == -1) {
+ fprintf(stderr, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+ return -EIO;
+ }
+ if (res != len) {
+ fprintf(stderr, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf, unsigned int flags)
+{
+ int res;
+ size_t len = fuse_buf_size(buf);
+ struct fuse_out_header *out = iov[0].iov_base;
+ struct fuse_ll_pipe *llp;
+ int splice_flags;
+ size_t pipesize;
+ size_t total_fd_size;
+ size_t idx;
+ size_t headerlen;
+ struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
+ if (f->broken_splice_nonblock)
+ goto fallback;
+
+ if (flags & FUSE_BUF_NO_SPLICE)
+ goto fallback;
+
+ total_fd_size = 0;
+ for (idx = buf->idx; idx < buf->count; idx++) {
+ if (buf->buf[idx].flags & FUSE_BUF_IS_FD) {
+ total_fd_size = buf->buf[idx].size;
+ if (idx == buf->idx)
+ total_fd_size -= buf->off;
+ }
+ }
+ if (total_fd_size < 2 * pagesize)
+ goto fallback;
+
+ if (f->conn.proto_minor < 14 ||
+ !(f->conn.want & FUSE_CAP_SPLICE_WRITE))
+ goto fallback;
+
+ llp = fuse_ll_get_pipe(f);
+ if (llp == NULL)
+ goto fallback;
+
+
+ headerlen = iov_length(iov, iov_count);
+
+ out->len = headerlen + len;
+
+ /*
+ * Heuristic for the required pipe size, does not work if the
+ * source contains less than page size fragments
+ */
+ pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
+ if (llp->size < pipesize) {
+ if (llp->can_grow) {
+ res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+ if (res == -1) {
+ llp->can_grow = 0;
+ goto fallback;
+ }
+ llp->size = res;
+ }
+ if (llp->size < pipesize)
+ goto fallback;
+ }
+
+
+ res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+ if (res == -1)
+ goto fallback;
+
+ if (res != headerlen) {
+ res = -EIO;
+ fprintf(stderr, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+ headerlen);
+ goto clear_pipe;
+ }
+
+ pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+ pipe_buf.buf[0].fd = llp->pipe[1];
+
+ res = fuse_buf_copy(&pipe_buf, buf,
+ FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK);
+ if (res < 0) {
+ if (res == -EAGAIN || res == -EINVAL) {
+ /*
+ * Should only get EAGAIN on kernels with
+ * broken SPLICE_F_NONBLOCK support (<=
+ * 2.6.35) where this error or a short read is
+ * returned even if the pipe itself is not
+ * full
+ *
+ * EINVAL might mean that splice can't handle
+ * this combination of input and output.
+ */
+ if (res == -EAGAIN)
+ f->broken_splice_nonblock = 1;
+
+ pthread_setspecific(f->pipe_key, NULL);
+ fuse_ll_pipe_free(llp);
+ goto fallback;
+ }
+ res = -res;
+ goto clear_pipe;
+ }
+
+ if (res != 0 && res < len) {
+ struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+ void *mbuf;
+ size_t now_len = res;
+ /*
+ * For regular files a short count is either
+ * 1) due to EOF, or
+ * 2) because of broken SPLICE_F_NONBLOCK (see above)
+ *
+ * For other inputs it's possible that we overflowed
+ * the pipe because of small buffer fragments.
+ */
+
+ res = posix_memalign(&mbuf, pagesize, len);
+ if (res != 0)
+ goto clear_pipe;
+
+ mem_buf.buf[0].mem = mbuf;
+ mem_buf.off = now_len;
+ res = fuse_buf_copy(&mem_buf, buf, 0);
+ if (res > 0) {
+ char *tmpbuf;
+ size_t extra_len = res;
+ /*
+ * Trickiest case: got more data. Need to get
+ * back the data from the pipe and then fall
+ * back to regular write.
+ */
+ tmpbuf = malloc(headerlen);
+ if (tmpbuf == NULL) {
+ free(mbuf);
+ res = ENOMEM;
+ goto clear_pipe;
+ }
+ res = read_back(llp->pipe[0], tmpbuf, headerlen);
+ if (res != 0) {
+ free(mbuf);
+ goto clear_pipe;
+ }
+ free(tmpbuf);
+ res = read_back(llp->pipe[0], mbuf, now_len);
+ if (res != 0) {
+ free(mbuf);
+ goto clear_pipe;
+ }
+ len = now_len + extra_len;
+ iov[iov_count].iov_base = mbuf;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ res = fuse_send_msg(f, ch, iov, iov_count);
+ free(mbuf);
+ return res;
+ }
+ free(mbuf);
+ res = now_len;
+ }
+ len = res;
+ out->len = headerlen + len;
+
+ if (f->debug) {
+ fprintf(stderr,
+ " unique: %llu, success, outsize: %i (splice)\n",
+ (unsigned long long) out->unique, out->len);
+ }
+
+ splice_flags = 0;
+ if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+ (f->conn.want & FUSE_CAP_SPLICE_MOVE))
+ splice_flags |= SPLICE_F_MOVE;
+
+ res = splice(llp->pipe[0], NULL,
+ fuse_chan_fd(ch), NULL, out->len, splice_flags);
+ if (res == -1) {
+ res = -errno;
+ perror("fuse: splice from pipe");
+ goto clear_pipe;
+ }
+ if (res != out->len) {
+ res = -EIO;
+ fprintf(stderr, "fuse: short splice from pipe: %u/%u\n",
+ res, out->len);
+ goto clear_pipe;
+ }
+ return 0;
+
+clear_pipe:
+ fuse_ll_clear_pipe(f);
+ return res;
+
+fallback:
+ return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len);
+}
+#else
+static int fuse_send_data_iov(struct fuse_ll *f, struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf, unsigned int flags)
+{
+ size_t len = fuse_buf_size(buf);
+ (void) flags;
+
+ return fuse_send_data_iov_fallback(f, ch, iov, iov_count, buf, len);
+}
+#endif
+
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags)
+{
+ struct iovec iov[2];
+ struct fuse_out_header out;
+ int res;
+
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(struct fuse_out_header);
+
+ out.unique = req->unique;
+ out.error = 0;
+
+ res = fuse_send_data_iov(req->f, req->ch, iov, 1, bufv, flags);
+ if (res <= 0) {
+ fuse_free_req(req);
+ return res;
+ } else {
+ return fuse_reply_err(req, res);
+ }
+}
+
int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
{
struct fuse_statfs_out arg;
@@ -402,7 +800,7 @@
return send_reply_ok(req, &arg, sizeof(arg));
}
-int fuse_reply_lock(fuse_req_t req, struct flock *lock)
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
{
struct fuse_lk_out arg;
@@ -429,13 +827,34 @@
return send_reply_ok(req, &arg, sizeof(arg));
}
+static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+ size_t count)
+{
+ struct fuse_ioctl_iovec *fiov;
+ size_t i;
+
+ fiov = malloc(sizeof(fiov[0]) * count);
+ if (!fiov)
+ return NULL;
+
+ for (i = 0; i < count; i++) {
+ fiov[i].base = (uintptr_t) iov[i].iov_base;
+ fiov[i].len = iov[i].iov_len;
+ }
+
+ return fiov;
+}
+
int fuse_reply_ioctl_retry(fuse_req_t req,
const struct iovec *in_iov, size_t in_count,
const struct iovec *out_iov, size_t out_count)
{
struct fuse_ioctl_out arg;
+ struct fuse_ioctl_iovec *in_fiov = NULL;
+ struct fuse_ioctl_iovec *out_fiov = NULL;
struct iovec iov[4];
size_t count = 1;
+ int res;
memset(&arg, 0, sizeof(arg));
arg.flags |= FUSE_IOCTL_RETRY;
@@ -445,19 +864,55 @@
iov[count].iov_len = sizeof(arg);
count++;
- if (in_count) {
- iov[count].iov_base = (void *)in_iov;
- iov[count].iov_len = sizeof(in_iov[0]) * in_count;
- count++;
+ if (req->f->conn.proto_minor < 16) {
+ if (in_count) {
+ iov[count].iov_base = (void *)in_iov;
+ iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+ count++;
+ }
+
+ if (out_count) {
+ iov[count].iov_base = (void *)out_iov;
+ iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+ count++;
+ }
+ } else {
+ /* Can't handle non-compat 64bit ioctls on 32bit */
+ if (sizeof(void *) == 4 && req->ioctl_64bit) {
+ res = fuse_reply_err(req, EINVAL);
+ goto out;
+ }
+
+ if (in_count) {
+ in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+ if (!in_fiov)
+ goto enomem;
+
+ iov[count].iov_base = (void *)in_fiov;
+ iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+ count++;
+ }
+ if (out_count) {
+ out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+ if (!out_fiov)
+ goto enomem;
+
+ iov[count].iov_base = (void *)out_fiov;
+ iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+ count++;
+ }
}
- if (out_count) {
- iov[count].iov_base = (void *)out_iov;
- iov[count].iov_len = sizeof(out_iov[0]) * out_count;
- count++;
- }
+ res = send_reply_iov(req, 0, iov, count);
+out:
+ free(in_fiov);
+ free(out_fiov);
- return send_reply_iov(req, 0, iov, count);
+ return res;
+
+enomem:
+ res = fuse_reply_err(req, ENOMEM);
+ goto out;
}
int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
@@ -489,10 +944,8 @@
int res;
padded_iov = malloc((count + 2) * sizeof(struct iovec));
- if (padded_iov == NULL) {
- printf("ENOMEM fuse_reply_err\n");
- return fuse_reply_err(req, -ENOMEM);
- }
+ if (padded_iov == NULL)
+ return fuse_reply_err(req, ENOMEM);
memset(&arg, 0, sizeof(arg));
arg.result = result;
@@ -523,10 +976,8 @@
if (req->f->op.lookup)
req->f->op.lookup(req, nodeid, name);
- else {
- printf("ENOSYS do_lookup\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -539,6 +990,40 @@
fuse_reply_none(req);
}
+static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+ const void *inarg)
+{
+ struct fuse_batch_forget_in *arg = (void *) inarg;
+ struct fuse_forget_one *param = (void *) PARAM(arg);
+ unsigned int i;
+
+ (void) nodeid;
+
+ if (req->f->op.forget_multi) {
+ req->f->op.forget_multi(req, arg->count,
+ (struct fuse_forget_data *) param);
+ } else if (req->f->op.forget) {
+ for (i = 0; i < arg->count; i++) {
+ struct fuse_forget_one *forget = ¶m[i];
+ struct fuse_req *dummy_req;
+
+ dummy_req = fuse_ll_alloc_req(req->f);
+ if (dummy_req == NULL)
+ break;
+
+ dummy_req->unique = req->unique;
+ dummy_req->ctx = req->ctx;
+ dummy_req->ch = NULL;
+
+ req->f->op.forget(dummy_req, forget->nodeid,
+ forget->nlookup);
+ }
+ fuse_reply_none(req);
+ } else {
+ fuse_reply_none(req);
+ }
+}
+
static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
{
struct fuse_file_info *fip = NULL;
@@ -557,10 +1042,8 @@
if (req->f->op.getattr)
req->f->op.getattr(req, nodeid, fip);
- else {
- printf("ENOSYS do_getattr\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -591,10 +1074,8 @@
FUSE_SET_ATTR_MTIME_NOW;
req->f->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
- } else {
- printf("ENOSYS do_setattr\n");
+ } else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -603,10 +1084,8 @@
if (req->f->op.access)
req->f->op.access(req, nodeid, arg->mask);
- else {
- printf("ENOSYS do_access\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -615,10 +1094,8 @@
if (req->f->op.readlink)
req->f->op.readlink(req, nodeid);
- else {
- printf("ENOSYS do_readlink\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -633,10 +1110,8 @@
if (req->f->op.mknod)
req->f->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
- else {
- printf("ENOSYS do_mknod\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -648,10 +1123,8 @@
if (req->f->op.mkdir)
req->f->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
- else {
- printf("ENOSYS do_mkdir\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -660,10 +1133,8 @@
if (req->f->op.unlink)
req->f->op.unlink(req, nodeid, name);
- else {
- printf("ENOSYS do_unlink\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -672,10 +1143,8 @@
if (req->f->op.rmdir)
req->f->op.rmdir(req, nodeid, name);
- else {
- printf("ENOSYS do_rmdir\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -685,10 +1154,8 @@
if (req->f->op.symlink)
req->f->op.symlink(req, linkname, nodeid, name);
- else {
- printf("ENOSYS do_symlink\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -699,10 +1166,8 @@
if (req->f->op.rename)
req->f->op.rename(req, nodeid, oldname, arg->newdir, newname);
- else {
- printf("ENOSYS do_rename\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -711,10 +1176,8 @@
if (req->f->op.link)
req->f->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
- else {
- printf("ENOSYS do_link\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -734,10 +1197,8 @@
name = (char *) inarg + sizeof(struct fuse_open_in);
req->f->op.create(req, nodeid, name, arg->mode, &fi);
- } else {
- printf("ENOSYS do_create\n");
+ } else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -769,10 +1230,8 @@
fi.flags = arg->flags;
}
req->f->op.read(req, nodeid, arg->size, arg->offset, &fi);
- } else {
- printf("ENOSYS do_read\n");
+ } else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -797,10 +1256,53 @@
if (req->f->op.write)
req->f->op.write(req, nodeid, param, arg->size,
arg->offset, &fi);
- else {
- printf("ENOSYS do_write\n");
+ else
fuse_reply_err(req, ENOSYS);
+}
+
+static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+ const struct fuse_buf *ibuf)
+{
+ struct fuse_ll *f = req->f;
+ struct fuse_bufvec bufv = {
+ .buf[0] = *ibuf,
+ .count = 1,
+ };
+ struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.fh_old = fi.fh;
+ fi.writepage = arg->write_flags & 1;
+
+ if (req->f->conn.proto_minor < 9) {
+ bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ FUSE_COMPAT_WRITE_IN_SIZE;
+ assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+ } else {
+ fi.lock_owner = arg->lock_owner;
+ fi.flags = arg->flags;
+ if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+ bufv.buf[0].mem = PARAM(arg);
+
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in);
}
+ if (bufv.buf[0].size < arg->size) {
+ fprintf(stderr, "fuse: do_write_buf: buffer size too small\n");
+ fuse_reply_err(req, EIO);
+ goto out;
+ }
+ bufv.buf[0].size = arg->size;
+
+ req->f->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
+out:
+ /* Need to reset the pipe if ->write_buf() didn't consume all data */
+ if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+ fuse_ll_clear_pipe(f);
}
static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -817,10 +1319,8 @@
if (req->f->op.flush)
req->f->op.flush(req, nodeid, &fi);
- else {
- printf("ENOSYS do_flush\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -836,6 +1336,10 @@
fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
fi.lock_owner = arg->lock_owner;
}
+ if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+ fi.flock_release = 1;
+ fi.lock_owner = arg->lock_owner;
+ }
if (req->f->op.release)
req->f->op.release(req, nodeid, &fi);
@@ -854,10 +1358,8 @@
if (req->f->op.fsync)
req->f->op.fsync(req, nodeid, arg->fsync_flags & 1, &fi);
- else {
- printf("ENOSYS do_fsync\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -885,10 +1387,8 @@
if (req->f->op.readdir)
req->f->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
- else {
- printf("ENOSYS do_fsync\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -918,10 +1418,8 @@
if (req->f->op.fsyncdir)
req->f->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &fi);
- else {
- printf("ENOSYS do_fsyncdir\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -949,10 +1447,8 @@
if (req->f->op.setxattr)
req->f->op.setxattr(req, nodeid, name, value, arg->size,
arg->flags);
- else {
- printf("ENOSYS do_setxattr\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -961,10 +1457,8 @@
if (req->f->op.getxattr)
req->f->op.getxattr(req, nodeid, PARAM(arg), arg->size);
- else {
- printf("ENOSYS do_getxattr\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -973,10 +1467,8 @@
if (req->f->op.listxattr)
req->f->op.listxattr(req, nodeid, arg->size);
- else {
- printf("ENOSYS do_listxattr\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -985,10 +1477,8 @@
if (req->f->op.removexattr)
req->f->op.removexattr(req, nodeid, name);
- else {
- printf("ENOSYS do_removetxattr\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void convert_fuse_file_lock(struct fuse_file_lock *fl,
@@ -1018,10 +1508,8 @@
convert_fuse_file_lock(&arg->lk, &flock);
if (req->f->op.getlk)
req->f->op.getlk(req, nodeid, &fi, &flock);
- else {
- printf("do_getlk ENOSYS\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
@@ -1035,12 +1523,33 @@
fi.fh = arg->fh;
fi.lock_owner = arg->owner;
- convert_fuse_file_lock(&arg->lk, &flock);
- if (req->f->op.setlk)
- req->f->op.setlk(req, nodeid, &fi, &flock, sleep);
- else {
- printf("do_getlk ENOSYS\n");
- fuse_reply_err(req, ENOSYS);
+ if (arg->lk_flags & FUSE_LK_FLOCK) {
+ int op = 0;
+
+ switch (arg->lk.type) {
+ case F_RDLCK:
+ op = LOCK_SH;
+ break;
+ case F_WRLCK:
+ op = LOCK_EX;
+ break;
+ case F_UNLCK:
+ op = LOCK_UN;
+ break;
+ }
+ if (!sleep)
+ op |= LOCK_NB;
+
+ if (req->f->op.flock)
+ req->f->op.flock(req, nodeid, &fi, op);
+ else
+ fuse_reply_err(req, ENOSYS);
+ } else {
+ convert_fuse_file_lock(&arg->lk, &flock);
+ if (req->f->op.setlk)
+ req->f->op.setlk(req, nodeid, &fi, &flock, sleep);
+ else
+ fuse_reply_err(req, ENOSYS);
}
}
@@ -1141,10 +1650,8 @@
if (req->f->op.bmap)
req->f->op.bmap(req, nodeid, arg->blocksize, arg->block);
- else {
- printf("do_bmap ENOSYS\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
@@ -1154,18 +1661,27 @@
void *in_buf = arg->in_size ? PARAM(arg) : NULL;
struct fuse_file_info fi;
+ if (flags & FUSE_IOCTL_DIR &&
+ !(req->f->conn.want & FUSE_CAP_IOCTL_DIR)) {
+ fuse_reply_err(req, ENOTTY);
+ return;
+ }
+
memset(&fi, 0, sizeof(fi));
fi.fh = arg->fh;
fi.fh_old = fi.fh;
+ if (sizeof(void *) == 4 && req->f->conn.proto_minor >= 16 &&
+ !(flags & FUSE_IOCTL_32BIT)) {
+ req->ioctl_64bit = 1;
+ }
+
if (req->f->op.ioctl)
req->f->op.ioctl(req, nodeid, arg->cmd,
(void *)(uintptr_t)arg->arg, &fi, flags,
in_buf, arg->in_size, arg->out_size);
- else {
- printf("do_ioctl ENOSYS\n");
+ else
fuse_reply_err(req, ENOSYS);
- }
}
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
@@ -1188,7 +1704,6 @@
if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
ph = malloc(sizeof(struct fuse_pollhandle));
if (ph == NULL) {
- printf("ENOMEM do_poll\n");
fuse_reply_err(req, ENOMEM);
return;
}
@@ -1199,11 +1714,24 @@
req->f->op.poll(req, nodeid, &fi, ph);
} else {
- printf("ENOSYS do_poll\n");
fuse_reply_err(req, ENOSYS);
}
}
+static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->f->op.fallocate)
+ req->f->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
{
struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
@@ -1232,7 +1760,6 @@
if (arg->major < 7) {
fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n",
arg->major, arg->minor);
- printf("EPROTO do_init\n");
fuse_reply_err(req, EPROTO);
return;
}
@@ -1260,15 +1787,36 @@
f->conn.capable |= FUSE_CAP_BIG_WRITES;
if (arg->flags & FUSE_DONT_MASK)
f->conn.capable |= FUSE_CAP_DONT_MASK;
+ if (arg->flags & FUSE_FLOCK_LOCKS)
+ f->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
} else {
f->conn.async_read = 0;
f->conn.max_readahead = 0;
}
+ if (req->f->conn.proto_minor >= 14) {
+#ifdef HAVE_SPLICE
+#ifdef HAVE_VMSPLICE
+ f->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
+ if (f->splice_write)
+ f->conn.want |= FUSE_CAP_SPLICE_WRITE;
+ if (f->splice_move)
+ f->conn.want |= FUSE_CAP_SPLICE_MOVE;
+#endif
+ f->conn.capable |= FUSE_CAP_SPLICE_READ;
+ if (f->splice_read)
+ f->conn.want |= FUSE_CAP_SPLICE_READ;
+#endif
+ }
+ if (req->f->conn.proto_minor >= 18)
+ f->conn.capable |= FUSE_CAP_IOCTL_DIR;
+
if (f->atomic_o_trunc)
f->conn.want |= FUSE_CAP_ATOMIC_O_TRUNC;
- if (f->op.getlk && f->op.setlk && !f->no_remote_lock)
+ if (f->op.getlk && f->op.setlk && !f->no_remote_posix_lock)
f->conn.want |= FUSE_CAP_POSIX_LOCKS;
+ if (f->op.flock && !f->no_remote_flock)
+ f->conn.want |= FUSE_CAP_FLOCK_LOCKS;
if (f->big_writes)
f->conn.want |= FUSE_CAP_BIG_WRITES;
@@ -1286,6 +1834,13 @@
if (f->op.init)
f->op.init(f->userdata, &f->conn);
+ if (f->no_splice_read)
+ f->conn.want &= ~FUSE_CAP_SPLICE_READ;
+ if (f->no_splice_write)
+ f->conn.want &= ~FUSE_CAP_SPLICE_WRITE;
+ if (f->no_splice_move)
+ f->conn.want &= ~FUSE_CAP_SPLICE_MOVE;
+
if (f->conn.async_read || (f->conn.want & FUSE_CAP_ASYNC_READ))
outarg.flags |= FUSE_ASYNC_READ;
if (f->conn.want & FUSE_CAP_POSIX_LOCKS)
@@ -1298,8 +1853,23 @@
outarg.flags |= FUSE_BIG_WRITES;
if (f->conn.want & FUSE_CAP_DONT_MASK)
outarg.flags |= FUSE_DONT_MASK;
+ if (f->conn.want & FUSE_CAP_FLOCK_LOCKS)
+ outarg.flags |= FUSE_FLOCK_LOCKS;
outarg.max_readahead = f->conn.max_readahead;
outarg.max_write = f->conn.max_write;
+ if (f->conn.proto_minor >= 13) {
+ if (f->conn.max_background >= (1 << 16))
+ f->conn.max_background = (1 << 16) - 1;
+ if (f->conn.congestion_threshold > f->conn.max_background)
+ f->conn.congestion_threshold = f->conn.max_background;
+ if (!f->conn.congestion_threshold) {
+ f->conn.congestion_threshold =
+ f->conn.max_background * 3 / 4;
+ }
+
+ outarg.max_background = f->conn.max_background;
+ outarg.congestion_threshold = f->conn.congestion_threshold;
+ }
if (f->debug) {
fprintf(stderr, " INIT: %u.%u\n", outarg.major, outarg.minor);
@@ -1307,6 +1877,10 @@
fprintf(stderr, " max_readahead=0x%08x\n",
outarg.max_readahead);
fprintf(stderr, " max_write=0x%08x\n", outarg.max_write);
+ fprintf(stderr, " max_background=%i\n",
+ outarg.max_background);
+ fprintf(stderr, " congestion_threshold=%i\n",
+ outarg.congestion_threshold);
}
send_reply_ok(req, &outarg, arg->minor < 5 ? 8 : sizeof(outarg));
@@ -1326,22 +1900,65 @@
send_reply_ok(req, NULL, 0);
}
+static void list_del_nreq(struct fuse_notify_req *nreq)
+{
+ struct fuse_notify_req *prev = nreq->prev;
+ struct fuse_notify_req *next = nreq->next;
+ prev->next = next;
+ next->prev = prev;
+}
+
+static void list_add_nreq(struct fuse_notify_req *nreq,
+ struct fuse_notify_req *next)
+{
+ struct fuse_notify_req *prev = next->prev;
+ nreq->next = next;
+ nreq->prev = prev;
+ prev->next = nreq;
+ next->prev = nreq;
+}
+
+static void list_init_nreq(struct fuse_notify_req *nreq)
+{
+ nreq->next = nreq;
+ nreq->prev = nreq;
+}
+
+static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+ const void *inarg, const struct fuse_buf *buf)
+{
+ struct fuse_ll *f = req->f;
+ struct fuse_notify_req *nreq;
+ struct fuse_notify_req *head;
+
+ pthread_mutex_lock(&f->lock);
+ head = &f->notify_list;
+ for (nreq = head->next; nreq != head; nreq = nreq->next) {
+ if (nreq->unique == req->unique) {
+ list_del_nreq(nreq);
+ break;
+ }
+ }
+ pthread_mutex_unlock(&f->lock);
+
+ if (nreq != head)
+ nreq->reply(nreq, req, nodeid, inarg, buf);
+}
+
static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch,
int notify_code, struct iovec *iov, int count)
{
struct fuse_out_header out;
+ if (!f->got_init)
+ return -ENOTCONN;
+
out.unique = 0;
out.error = notify_code;
iov[0].iov_base = &out;
iov[0].iov_len = sizeof(struct fuse_out_header);
- out.len = iov_length(iov, count);
- if (f->debug)
- fprintf(stderr, "NOTIFY: code=%d count=%d length=%u\n",
- notify_code, count, out.len);
-
- return fuse_chan_send(ch, iov, count);
+ return fuse_send_msg(f, ch, iov, count);
}
int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
@@ -1411,6 +2028,170 @@
return send_notify_iov(f, ch, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
}
+int fuse_lowlevel_notify_delete(struct fuse_chan *ch,
+ fuse_ino_t parent, fuse_ino_t child,
+ const char *name, size_t namelen)
+{
+ struct fuse_notify_delete_out outarg;
+ struct fuse_ll *f;
+ struct iovec iov[3];
+
+ if (!ch)
+ return -EINVAL;
+
+ f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+ if (!f)
+ return -ENODEV;
+
+ if (f->conn.proto_minor < 18)
+ return -ENOSYS;
+
+ outarg.parent = parent;
+ outarg.child = child;
+ outarg.namelen = namelen;
+ outarg.padding = 0;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+ iov[2].iov_base = (void *)name;
+ iov[2].iov_len = namelen + 1;
+
+ return send_notify_iov(f, ch, FUSE_NOTIFY_DELETE, iov, 3);
+}
+
+int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
+ off64_t offset, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags)
+{
+ struct fuse_out_header out;
+ struct fuse_notify_store_out outarg;
+ struct fuse_ll *f;
+ struct iovec iov[3];
+ size_t size = fuse_buf_size(bufv);
+ int res;
+
+ if (!ch)
+ return -EINVAL;
+
+ f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+ if (!f)
+ return -ENODEV;
+
+ if (f->conn.proto_minor < 15)
+ return -ENOSYS;
+
+ out.unique = 0;
+ out.error = FUSE_NOTIFY_STORE;
+
+ outarg.nodeid = ino;
+ outarg.offset = offset;
+ outarg.size = size;
+
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(out);
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ res = fuse_send_data_iov(f, ch, iov, 2, bufv, flags);
+ if (res > 0)
+ res = -res;
+
+ return res;
+}
+
+struct fuse_retrieve_req {
+ struct fuse_notify_req nreq;
+ void *cookie;
+};
+
+static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+ fuse_req_t req, fuse_ino_t ino,
+ const void *inarg,
+ const struct fuse_buf *ibuf)
+{
+ struct fuse_ll *f = req->f;
+ struct fuse_retrieve_req *rreq =
+ container_of(nreq, struct fuse_retrieve_req, nreq);
+ const struct fuse_notify_retrieve_in *arg = inarg;
+ struct fuse_bufvec bufv = {
+ .buf[0] = *ibuf,
+ .count = 1,
+ };
+
+ if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+ bufv.buf[0].mem = PARAM(arg);
+
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_notify_retrieve_in);
+
+ if (bufv.buf[0].size < arg->size) {
+ fprintf(stderr, "fuse: retrieve reply: buffer size too small\n");
+ fuse_reply_none(req);
+ goto out;
+ }
+ bufv.buf[0].size = arg->size;
+
+ if (req->f->op.retrieve_reply) {
+ req->f->op.retrieve_reply(req, rreq->cookie, ino,
+ arg->offset, &bufv);
+ } else {
+ fuse_reply_none(req);
+ }
+out:
+ free(rreq);
+ if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+ fuse_ll_clear_pipe(f);
+}
+
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+ size_t size, off64_t offset, void *cookie)
+{
+ struct fuse_notify_retrieve_out outarg;
+ struct fuse_ll *f;
+ struct iovec iov[2];
+ struct fuse_retrieve_req *rreq;
+ int err;
+
+ if (!ch)
+ return -EINVAL;
+
+ f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+ if (!f)
+ return -ENODEV;
+
+ if (f->conn.proto_minor < 15)
+ return -ENOSYS;
+
+ rreq = malloc(sizeof(*rreq));
+ if (rreq == NULL)
+ return -ENOMEM;
+
+ pthread_mutex_lock(&f->lock);
+ rreq->cookie = cookie;
+ rreq->nreq.unique = f->notify_ctr++;
+ rreq->nreq.reply = fuse_ll_retrieve_reply;
+ list_add_nreq(&rreq->nreq, &f->notify_list);
+ pthread_mutex_unlock(&f->lock);
+
+ outarg.notify_unique = rreq->nreq.unique;
+ outarg.nodeid = ino;
+ outarg.offset = offset;
+ outarg.size = size;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ err = send_notify_iov(f, ch, FUSE_NOTIFY_RETRIEVE, iov, 2);
+ if (err) {
+ pthread_mutex_lock(&f->lock);
+ list_del_nreq(&rreq->nreq);
+ pthread_mutex_unlock(&f->lock);
+ free(rreq);
+ }
+
+ return err;
+}
+
void *fuse_req_userdata(fuse_req_t req)
{
return req->f->userdata;
@@ -1431,7 +2212,9 @@
{
return fuse_req_ctx(req);
}
+#ifndef __NetBSD__
FUSE_SYMVER(".symver fuse_req_ctx_compat24,fuse_req_ctx@FUSE_2.4");
+#endif
void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
@@ -1499,7 +2282,10 @@
[FUSE_BMAP] = { do_bmap, "BMAP" },
[FUSE_IOCTL] = { do_ioctl, "IOCTL" },
[FUSE_POLL] = { do_poll, "POLL" },
+ [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
[FUSE_DESTROY] = { do_destroy, "DESTROY" },
+ [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+ [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
[CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
};
@@ -1513,37 +2299,84 @@
return fuse_ll_ops[opcode].name;
}
-static void fuse_ll_process(void *data, const char *buf, size_t len,
- struct fuse_chan *ch)
+static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+ struct fuse_bufvec *src)
+{
+ int res = fuse_buf_copy(dst, src, 0);
+ if (res < 0) {
+ fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res));
+ return res;
+ }
+ if (res < fuse_buf_size(dst)) {
+ fprintf(stderr, "fuse: copy from pipe: short read\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf,
+ struct fuse_chan *ch)
{
struct fuse_ll *f = (struct fuse_ll *) data;
- struct fuse_in_header *in = (struct fuse_in_header *) buf;
- const void *inarg = buf + sizeof(struct fuse_in_header);
+ const size_t write_header_size = sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in);
+ struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+ struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+ struct fuse_in_header *in;
+ const void *inarg;
struct fuse_req *req;
+ void *mbuf = NULL;
int err;
+ int res;
- if (f->debug)
- fprintf(stderr,
- "unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %zu\n",
- (unsigned long long) in->unique,
- opname((enum fuse_opcode) in->opcode), in->opcode,
- (unsigned long) in->nodeid, len);
+ if (buf->flags & FUSE_BUF_IS_FD) {
+ if (buf->size < tmpbuf.buf[0].size)
+ tmpbuf.buf[0].size = buf->size;
- req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
- if (req == NULL) {
- fprintf(stderr, "fuse: failed to allocate request\n");
- return;
+ mbuf = malloc(tmpbuf.buf[0].size);
+ if (mbuf == NULL) {
+ fprintf(stderr, "fuse: failed to allocate header\n");
+ goto clear_pipe;
+ }
+ tmpbuf.buf[0].mem = mbuf;
+
+ res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+ if (res < 0)
+ goto clear_pipe;
+
+ in = mbuf;
+ } else {
+ in = buf->mem;
}
- req->f = f;
+ if (f->debug) {
+ fprintf(stderr,
+ "unique: %llu, opcode: %s (%i), nodeid: %lu, insize: %zu, pid: %u\n",
+ (unsigned long long) in->unique,
+ opname((enum fuse_opcode) in->opcode), in->opcode,
+ (unsigned long) in->nodeid, buf->size, in->pid);
+ }
+
+ req = fuse_ll_alloc_req(f);
+ if (req == NULL) {
+ struct fuse_out_header out = {
+ .unique = in->unique,
+ .error = -ENOMEM,
+ };
+ struct iovec iov = {
+ .iov_base = &out,
+ .iov_len = sizeof(struct fuse_out_header),
+ };
+
+ fuse_send_msg(f, ch, &iov, 1);
+ goto clear_pipe;
+ }
+
req->unique = in->unique;
req->ctx.uid = in->uid;
req->ctx.gid = in->gid;
req->ctx.pid = in->pid;
req->ch = ch;
- req->ctr = 1;
- list_init_req(req);
- fuse_mutex_init(&req->lock);
err = EIO;
if (!f->got_init) {
@@ -1560,7 +2393,8 @@
in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
- in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR)
+ in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+ in->opcode != FUSE_NOTIFY_REPLY)
goto reply_err;
err = ENOSYS;
@@ -1572,16 +2406,61 @@
intr = check_interrupt(f, req);
list_add_req(req, &f->list);
pthread_mutex_unlock(&f->lock);
- if (intr) {
- printf("EAGAIN fuse_llprocess\n");
+ if (intr)
fuse_reply_err(intr, EAGAIN);
- }
}
- fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
+ if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+ (in->opcode != FUSE_WRITE || !f->op.write_buf) &&
+ in->opcode != FUSE_NOTIFY_REPLY) {
+ void *newmbuf;
+
+ err = ENOMEM;
+ newmbuf = realloc(mbuf, buf->size);
+ if (newmbuf == NULL)
+ goto reply_err;
+ mbuf = newmbuf;
+
+ tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+ tmpbuf.buf[0].mem = mbuf + write_header_size;
+
+ res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+ err = -res;
+ if (res < 0)
+ goto reply_err;
+
+ in = mbuf;
+ }
+
+ inarg = (void *) &in[1];
+ if (in->opcode == FUSE_WRITE && f->op.write_buf)
+ do_write_buf(req, in->nodeid, inarg, buf);
+ else if (in->opcode == FUSE_NOTIFY_REPLY)
+ do_notify_reply(req, in->nodeid, inarg, buf);
+ else
+ fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
+out_free:
+ free(mbuf);
return;
- reply_err:
+reply_err:
fuse_reply_err(req, err);
+clear_pipe:
+ if (buf->flags & FUSE_BUF_IS_FD)
+ fuse_ll_clear_pipe(f);
+ goto out_free;
+}
+
+static void fuse_ll_process(void *data, const char *buf, size_t len,
+ struct fuse_chan *ch)
+{
+ struct fuse_buf fbuf = {
+ .mem = (void *) buf,
+ .size = len,
+ };
+
+ fuse_ll_process_buf(data, &fbuf, ch);
}
enum {
@@ -1589,17 +2468,29 @@
KEY_VERSION,
};
-static struct fuse_opt fuse_ll_opts[] = {
+static const struct fuse_opt fuse_ll_opts[] = {
{ "debug", offsetof(struct fuse_ll, debug), 1 },
{ "-d", offsetof(struct fuse_ll, debug), 1 },
{ "allow_root", offsetof(struct fuse_ll, allow_root), 1 },
{ "max_write=%u", offsetof(struct fuse_ll, conn.max_write), 0 },
{ "max_readahead=%u", offsetof(struct fuse_ll, conn.max_readahead), 0 },
+ { "max_background=%u", offsetof(struct fuse_ll, conn.max_background), 0 },
+ { "congestion_threshold=%u",
+ offsetof(struct fuse_ll, conn.congestion_threshold), 0 },
{ "async_read", offsetof(struct fuse_ll, conn.async_read), 1 },
{ "sync_read", offsetof(struct fuse_ll, conn.async_read), 0 },
{ "atomic_o_trunc", offsetof(struct fuse_ll, atomic_o_trunc), 1},
- { "no_remote_lock", offsetof(struct fuse_ll, no_remote_lock), 1},
+ { "no_remote_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
+ { "no_remote_lock", offsetof(struct fuse_ll, no_remote_flock), 1},
+ { "no_remote_flock", offsetof(struct fuse_ll, no_remote_flock), 1},
+ { "no_remote_posix_lock", offsetof(struct fuse_ll, no_remote_posix_lock), 1},
{ "big_writes", offsetof(struct fuse_ll, big_writes), 1},
+ { "splice_write", offsetof(struct fuse_ll, splice_write), 1},
+ { "no_splice_write", offsetof(struct fuse_ll, no_splice_write), 1},
+ { "splice_move", offsetof(struct fuse_ll, splice_move), 1},
+ { "no_splice_move", offsetof(struct fuse_ll, no_splice_move), 1},
+ { "splice_read", offsetof(struct fuse_ll, splice_read), 1},
+ { "no_splice_read", offsetof(struct fuse_ll, no_splice_read), 1},
FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
@@ -1619,11 +2510,19 @@
fprintf(stderr,
" -o max_write=N set maximum size of write requests\n"
" -o max_readahead=N set maximum readahead\n"
+" -o max_background=N set number of maximum background requests\n"
+" -o congestion_threshold=N set kernel's congestion threshold\n"
" -o async_read perform reads asynchronously (default)\n"
" -o sync_read perform reads synchronously\n"
" -o atomic_o_trunc enable atomic open+truncate support\n"
" -o big_writes enable larger than 4kB writes\n"
-" -o no_remote_lock disable remote file locking\n");
+" -o no_remote_lock disable remote file locking\n"
+" -o no_remote_flock disable remote file locking (BSD)\n"
+" -o no_remote_posix_lock disable remove file locking (POSIX)\n"
+" -o [no_]splice_write use splice to write to the fuse device\n"
+" -o [no_]splice_move move data while splicing to the fuse device\n"
+" -o [no_]splice_read use splice to read from the fuse device\n"
+);
}
static int fuse_ll_opt_proc(void *data, const char *arg, int key,
@@ -1655,17 +2554,142 @@
static void fuse_ll_destroy(void *data)
{
struct fuse_ll *f = (struct fuse_ll *) data;
+ struct fuse_ll_pipe *llp;
if (f->got_init && !f->got_destroy) {
if (f->op.destroy)
f->op.destroy(f->userdata);
}
-
+ llp = pthread_getspecific(f->pipe_key);
+ if (llp != NULL)
+ fuse_ll_pipe_free(llp);
+ pthread_key_delete(f->pipe_key);
pthread_mutex_destroy(&f->lock);
free(f->cuse_data);
free(f);
}
+static void fuse_ll_pipe_destructor(void *data)
+{
+ struct fuse_ll_pipe *llp = data;
+ fuse_ll_pipe_free(llp);
+}
+
+#ifdef HAVE_SPLICE
+static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan **chp)
+{
+ struct fuse_chan *ch = *chp;
+ struct fuse_ll *f = fuse_session_data(se);
+ size_t bufsize = buf->size;
+ struct fuse_ll_pipe *llp;
+ struct fuse_buf tmpbuf;
+ int err;
+ int res;
+
+ if (f->conn.proto_minor < 14 || !(f->conn.want & FUSE_CAP_SPLICE_READ))
+ goto fallback;
+
+ llp = fuse_ll_get_pipe(f);
+ if (llp == NULL)
+ goto fallback;
+
+ if (llp->size < bufsize) {
+ if (llp->can_grow) {
+ res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+ if (res == -1) {
+ llp->can_grow = 0;
+ goto fallback;
+ }
+ llp->size = res;
+ }
+ if (llp->size < bufsize)
+ goto fallback;
+ }
+
+ res = splice(fuse_chan_fd(ch), NULL, llp->pipe[1], NULL, bufsize, 0);
+ err = errno;
+
+ if (fuse_session_exited(se))
+ return 0;
+
+ if (res == -1) {
+ if (err == ENODEV) {
+ fuse_session_exit(se);
+ return 0;
+ }
+ if (err != EINTR && err != EAGAIN)
+ perror("fuse: splice from device");
+ return -err;
+ }
+
+ if (res < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "short splice from fuse device\n");
+ return -EIO;
+ }
+
+ tmpbuf = (struct fuse_buf) {
+ .size = res,
+ .flags = FUSE_BUF_IS_FD,
+ .fd = llp->pipe[0],
+ };
+
+ /*
+ * Don't bother with zero copy for small requests.
+ * fuse_loop_mt() needs to check for FORGET so this more than
+ * just an optimization.
+ */
+ if (res < sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in) + pagesize) {
+ struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+ struct fuse_bufvec dst = { .buf[0] = *buf, .count = 1 };
+
+ res = fuse_buf_copy(&dst, &src, 0);
+ if (res < 0) {
+ fprintf(stderr, "fuse: copy from pipe: %s\n",
+ strerror(-res));
+ fuse_ll_clear_pipe(f);
+ return res;
+ }
+ if (res < tmpbuf.size) {
+ fprintf(stderr, "fuse: copy from pipe: short read\n");
+ fuse_ll_clear_pipe(f);
+ return -EIO;
+ }
+ buf->size = tmpbuf.size;
+ return buf->size;
+ }
+
+ *buf = tmpbuf;
+
+ return res;
+
+fallback:
+ res = fuse_chan_recv(chp, buf->mem, bufsize);
+ if (res <= 0)
+ return res;
+
+ buf->size = res;
+
+ return res;
+}
+#else
+static int fuse_ll_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan **chp)
+{
+ (void) se;
+
+ int res = fuse_chan_recv(chp, buf->mem, buf->size);
+ if (res <= 0)
+ return res;
+
+ buf->size = res;
+
+ return res;
+}
+#endif
+
+
/*
* always call fuse_lowlevel_new_common() internally, to work around a
* misfeature in the FreeBSD runtime linker, which links the old
@@ -1675,6 +2699,7 @@
const struct fuse_lowlevel_ops *op,
size_t op_size, void *userdata)
{
+ int err;
struct fuse_ll *f;
struct fuse_session *se;
struct fuse_session_ops sop = {
@@ -1699,10 +2724,19 @@
f->atomic_o_trunc = 0;
list_init_req(&f->list);
list_init_req(&f->interrupts);
+ list_init_nreq(&f->notify_list);
+ f->notify_ctr = 1;
fuse_mutex_init(&f->lock);
- if (fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1)
+ err = pthread_key_create(&f->pipe_key, fuse_ll_pipe_destructor);
+ if (err) {
+ fprintf(stderr, "fuse: failed to create thread specific key: %s\n",
+ strerror(err));
goto out_free;
+ }
+
+ if (fuse_opt_parse(args, f, fuse_ll_opts, fuse_ll_opt_proc) == -1)
+ goto out_key_destroy;
if (f->debug)
fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION);
@@ -1713,11 +2747,17 @@
se = fuse_session_new(&sop, f);
if (!se)
- goto out_free;
+ goto out_key_destroy;
+
+ se->receive_buf = fuse_ll_receive_buf;
+ se->process_buf = fuse_ll_process_buf;
return se;
+out_key_destroy:
+ pthread_key_delete(f->pipe_key);
out_free:
+ pthread_mutex_destroy(&f->lock);
free(f);
out:
return NULL;
@@ -1800,7 +2840,7 @@
}
#endif
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
static void fill_open_compat(struct fuse_open_out *arg,
const struct fuse_file_info_compat *f)
@@ -1902,7 +2942,7 @@
FUSE_SYMVER(".symver fuse_reply_open_compat,fuse_reply_open@FUSE_2.4");
FUSE_SYMVER(".symver fuse_lowlevel_new_compat,fuse_lowlevel_new@FUSE_2.4");
-#else /* __FreeBSD__ */
+#else /* __FreeBSD__ || __NetBSD__ */
int fuse_sync_compat_args(struct fuse_args *args)
{
@@ -1910,7 +2950,7 @@
return 0;
}
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
struct fuse_session *fuse_lowlevel_new_compat25(struct fuse_args *args,
const struct fuse_lowlevel_ops_compat25 *op,
diff --git a/fuse/fuse_misc.h b/fuse/fuse_misc.h
index c2cfee1..eedf0e0 100644
--- a/fuse/fuse_misc.h
+++ b/fuse/fuse_misc.h
@@ -9,8 +9,12 @@
#include "config.h"
#include <pthread.h>
-/* Versioned symbols confuse the dynamic linker in uClibc */
-#ifndef __UCLIBC__
+/*
+ Versioned symbols cannot be used in some cases because it
+ - confuse the dynamic linker in uClibc
+ - not supported on MacOSX (in MachO binary format)
+*/
+#if (!defined(__UCLIBC__) && !defined(__APPLE__))
#define FUSE_SYMVER(x) __asm__(x)
#else
#define FUSE_SYMVER(x)
diff --git a/fuse/fuse_mt.c b/fuse/fuse_mt.c
index 7f94000..f6dbe71 100644
--- a/fuse/fuse_mt.c
+++ b/fuse/fuse_mt.c
@@ -24,8 +24,6 @@
void *data;
};
-#ifdef __MULTI_THREAD
-
static void mt_session_proc(void *data, const char *buf, size_t len,
struct fuse_chan *ch)
{
@@ -112,9 +110,13 @@
if (f == NULL)
return -1;
- return fuse_session_loop_mt(fuse_get_session(f));
+ int res = fuse_start_cleanup_thread(f);
+ if (res)
+ return -1;
+
+ res = fuse_session_loop_mt(fuse_get_session(f));
+ fuse_stop_cleanup_thread(f);
+ return res;
}
FUSE_SYMVER(".symver fuse_loop_mt_proc,__fuse_loop_mt@");
-
-#endif
diff --git a/fuse/fuse_opt.c b/fuse/fuse_opt.c
index b15e7db..a2118ce 100644
--- a/fuse/fuse_opt.c
+++ b/fuse/fuse_opt.c
@@ -54,11 +54,16 @@
assert(!args->argv || args->allocated);
- newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
- newarg = newargv ? strdup(arg) : NULL;
- if (!newargv || !newarg)
+ newarg = strdup(arg);
+ if (!newarg)
return alloc_failed();
+ newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+ if (!newargv) {
+ free(newarg);
+ return alloc_failed();
+ }
+
args->argv = newargv;
args->allocated = 1;
args->argv[args->argc++] = newarg;
@@ -304,9 +309,21 @@
return -1;
d = opts;
} else {
- if (s[0] == '\\' && s[1] != '\0')
+ if (s[0] == '\\' && s[1] != '\0') {
s++;
- *d++ = *s;
+ if (s[0] >= '0' && s[0] <= '3' &&
+ s[1] >= '0' && s[1] <= '7' &&
+ s[2] >= '0' && s[2] <= '7') {
+ *d++ = (s[0] - '0') * 0100 +
+ (s[1] - '0') * 0010 +
+ (s[2] - '0');
+ s += 2;
+ } else {
+ *d++ = *s;
+ }
+ } else {
+ *d++ = *s;
+ }
}
s++;
}
diff --git a/fuse/fuse_session.c b/fuse/fuse_session.c
index 3758627..c55f250 100644
--- a/fuse/fuse_session.c
+++ b/fuse/fuse_session.c
@@ -80,6 +80,34 @@
se->op.process(se->data, buf, len, ch);
}
+void fuse_session_process_buf(struct fuse_session *se,
+ const struct fuse_buf *buf, struct fuse_chan *ch)
+{
+ if (se->process_buf) {
+ se->process_buf(se->data, buf, ch);
+ } else {
+ assert(!(buf->flags & FUSE_BUF_IS_FD));
+ fuse_session_process(se->data, buf->mem, buf->size, ch);
+ }
+}
+
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan **chp)
+{
+ int res;
+
+ if (se->receive_buf) {
+ res = se->receive_buf(se, buf, chp);
+ } else {
+ res = fuse_chan_recv(chp, buf->mem, buf->size);
+ if (res > 0)
+ buf->size = res;
+ }
+
+ return res;
+}
+
+
void fuse_session_destroy(struct fuse_session *se)
{
if (se->op.destroy)
diff --git a/fuse/helper.c b/fuse/helper.c
index 7b994fd..ace19dd 100644
--- a/fuse/helper.c
+++ b/fuse/helper.c
@@ -179,13 +179,37 @@
int fuse_daemonize(int foreground)
{
- int res;
-
if (!foreground) {
- res = daemon(0, 0);
- if (res == -1) {
- perror("fuse: failed to daemonize program\n");
+ int nullfd;
+
+ /*
+ * demonize current process by forking it and killing the
+ * parent. This makes current process as a child of 'init'.
+ */
+ switch(fork()) {
+ case -1:
+ perror("fuse_daemonize: fork");
return -1;
+ case 0:
+ break;
+ default:
+ _exit(0);
+ }
+
+ if (setsid() == -1) {
+ perror("fuse_daemonize: setsid");
+ return -1;
+ }
+
+ (void) chdir("/");
+
+ nullfd = open("/dev/null", O_RDWR, 0);
+ if (nullfd != -1) {
+ (void) dup2(nullfd, 0);
+ (void) dup2(nullfd, 1);
+ (void) dup2(nullfd, 2);
+ if (nullfd > 2)
+ close(nullfd);
}
}
return 0;
@@ -227,7 +251,8 @@
{
int fd = ch ? fuse_chan_fd(ch) : -1;
fuse_kern_unmount(mountpoint, fd);
- fuse_chan_destroy(ch);
+ if (ch)
+ fuse_chan_destroy(ch);
}
void fuse_unmount(const char *mountpoint, struct fuse_chan *ch)
@@ -324,11 +349,9 @@
if (fuse == NULL)
return 1;
-#ifdef __MULTI_THREAD
if (multithreaded)
res = fuse_loop_mt(fuse);
else
-#endif
res = fuse_loop(fuse);
fuse_teardown_common(fuse, mountpoint);
@@ -359,7 +382,7 @@
#include "fuse_compat.h"
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
struct fuse *fuse_setup_compat22(int argc, char *argv[],
const struct fuse_operations_compat22 *op,
@@ -417,7 +440,7 @@
FUSE_SYMVER(".symver fuse_main_compat2,fuse_main@");
FUSE_SYMVER(".symver fuse_main_real_compat22,fuse_main_real@FUSE_2.2");
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
struct fuse *fuse_setup_compat25(int argc, char *argv[],
diff --git a/fuse/include/fuse.h b/fuse/include/fuse.h
index 899830f..cad816c 100644
--- a/fuse/include/fuse.h
+++ b/fuse/include/fuse.h
@@ -20,7 +20,7 @@
*/
#ifndef FUSE_USE_VERSION
-#define FUSE_USE_VERSION 28
+#define FUSE_USE_VERSION 21
#endif
#include "fuse_common.h"
@@ -32,6 +32,7 @@
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/uio.h>
+#include <pthread.h>
#ifdef __cplusplus
extern "C" {
@@ -412,7 +413,7 @@
* information without calling this method. This ensures, that
* for local locks the l_pid field is correctly filled in. The
* results may not be accurate in case of race conditions and in
- * the presence of hard links, but it's unlikly that an
+ * the presence of hard links, but it's unlikely that an
* application would rely on accurate GETLK results in these
* cases. If a conflicting lock is not found, this method will be
* called, and the filesystem may fill out l_pid by a meaningful
@@ -434,6 +435,11 @@
* Change the access and modification times of a file with
* nanosecond resolution
*
+ * This supersedes the old utime() interface. New applications
+ * should use this.
+ *
+ * See the utimensat(2) man page for details.
+ *
* Introduced in version 2.6
*/
int (*utimens) (const char *, const struct timespec tv[2]);
@@ -449,18 +455,41 @@
int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
/**
- * Flag indicating, that the filesystem can accept a NULL path
+ * Flag indicating that the filesystem can accept a NULL path
* as the first argument for the following operations:
*
* read, write, flush, release, fsync, readdir, releasedir,
- * fsyncdir, ftruncate, fgetattr and lock
+ * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
+ *
+ * If this flag is set these operations continue to work on
+ * unlinked files even if "-ohard_remove" option was specified.
*/
- unsigned int flag_nullpath_ok : 1;
+ unsigned int flag_nullpath_ok:1;
+
+ /**
+ * Flag indicating that the path need not be calculated for
+ * the following operations:
+ *
+ * read, write, flush, release, fsync, readdir, releasedir,
+ * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll
+ *
+ * Closely related to flag_nullpath_ok, but if this flag is
+ * set then the path will not be calculaged even if the file
+ * wasn't unlinked. However the path can still be non-NULL if
+ * it needs to be calculated for some other reason.
+ */
+ unsigned int flag_nopath:1;
+
+ /**
+ * Flag indicating that the filesystem accepts special
+ * UTIME_NOW and UTIME_OMIT values in its utimens operation.
+ */
+ unsigned int flag_utime_omit_ok:1;
/**
* Reserved flags, don't set
*/
- unsigned int flag_reserved : 31;
+ unsigned int flag_reserved:29;
/**
* Ioctl
@@ -496,6 +525,70 @@
*/
int (*poll) (const char *, struct fuse_file_info *,
struct fuse_pollhandle *ph, unsigned *reventsp);
+
+ /** Write contents of buffer to an open file
+ *
+ * Similar to the write() method, but data is supplied in a
+ * generic buffer. Use fuse_buf_copy() to transfer data to
+ * the destination.
+ *
+ * Introduced in version 2.9
+ */
+ int (*write_buf) (const char *, struct fuse_bufvec *buf, off64_t off,
+ struct fuse_file_info *);
+
+ /** Store data from an open file in a buffer
+ *
+ * Similar to the read() method, but data is stored and
+ * returned in a generic buffer.
+ *
+ * No actual copying of data has to take place, the source
+ * file descriptor may simply be stored in the buffer for
+ * later data transfer.
+ *
+ * The buffer must be allocated dynamically and stored at the
+ * location pointed to by bufp. If the buffer contains memory
+ * regions, they too must be allocated using malloc(). The
+ * allocated memory will be freed by the caller.
+ *
+ * Introduced in version 2.9
+ */
+ int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+ size_t size, off64_t off, struct fuse_file_info *);
+ /**
+ * Perform BSD file locking operation
+ *
+ * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
+ *
+ * Nonblocking requests will be indicated by ORing LOCK_NB to
+ * the above operations
+ *
+ * For more information see the flock(2) manual page.
+ *
+ * Additionally fi->owner will be set to a value unique to
+ * this open file. This same value will be supplied to
+ * ->release() when the file is released.
+ *
+ * Note: if this method is not implemented, the kernel will still
+ * allow file locking to work locally. Hence it is only
+ * interesting for network filesystems and similar.
+ *
+ * Introduced in version 2.9
+ */
+ int (*flock) (const char *, struct fuse_file_info *, int op);
+
+ /**
+ * Allocates space for an open file
+ *
+ * This function ensures that required space is allocated for specified
+ * file. If this function returns success then any subsequent write
+ * request to specified range is guaranteed not to fail because of lack
+ * of space on the file system media.
+ *
+ * Introduced in version 2.9.1
+ */
+ int (*fallocate) (const char *, int, off64_t, off64_t,
+ struct fuse_file_info *);
};
/** Extra context that may be needed by some filesystems
@@ -671,6 +764,34 @@
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
size_t op_size, void *user_data);
+/**
+ * Start the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ * @return 0 on success and -1 on error
+ */
+int fuse_start_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Stop the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ */
+void fuse_stop_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Iterate over cache removing stale entries
+ * use in conjunction with "-oremember"
+ *
+ * NOTE: This is already done for the standard sessions
+ *
+ * @param fuse struct fuse pointer for fuse instance
+ * @return the number of seconds until the next cleanup
+ */
+int fuse_clean_cache(struct fuse *fuse);
+
/*
* Stacking API
*/
@@ -707,8 +828,14 @@
struct fuse_file_info *fi);
int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
off64_t off, struct fuse_file_info *fi);
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec **bufp, size_t size, off64_t off,
+ struct fuse_file_info *fi);
int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
size_t size, off64_t off, struct fuse_file_info *fi);
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec *buf, off64_t off,
+ struct fuse_file_info *fi);
int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
struct fuse_file_info *fi);
int fuse_fs_flush(struct fuse_fs *fs, const char *path,
@@ -727,6 +854,8 @@
struct fuse_file_info *fi);
int fuse_fs_lock(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi, int cmd, struct flock *lock);
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int op);
int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode);
int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid);
int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off64_t size);
@@ -755,6 +884,8 @@
int fuse_fs_poll(struct fuse_fs *fs, const char *path,
struct fuse_file_info *fi, struct fuse_pollhandle *ph,
unsigned *reventsp);
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+ off64_t offset, off64_t length, struct fuse_file_info *fi);
void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn);
void fuse_fs_destroy(struct fuse_fs *fs);
diff --git a/fuse/include/fuse_common.h b/fuse/include/fuse_common.h
index 48c0746..dab3a56 100644
--- a/fuse/include/fuse_common.h
+++ b/fuse/include/fuse_common.h
@@ -17,16 +17,16 @@
#include "fuse_opt.h"
#include <stdint.h>
+#include <sys/types.h>
/** Major version of FUSE library interface */
#define FUSE_MAJOR_VERSION 2
/** Minor version of FUSE library interface */
-#define FUSE_MINOR_VERSION 8
+#define FUSE_MINOR_VERSION 9
#define FUSE_MAKE_VERSION(maj, min) ((maj) * 10 + (min))
-#define FUSE_VERSION 26
-//#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
/* This interface uses 64 bit off64_t */
#if _FILE_OFFSET_BITS != 64
@@ -70,8 +70,14 @@
seekable. Introduced in version 2.8 */
unsigned int nonseekable : 1;
+ /* Indicates that flock locks for this file should be
+ released. If set, lock_owner shall contain a valid value.
+ May only be set in ->release(). Introduced in version
+ 2.9 */
+ unsigned int flock_release : 1;
+
/** Padding. Do not use*/
- unsigned int padding : 28;
+ unsigned int padding : 27;
/** File handle. May be filled in by filesystem in open().
Available in all other file operations */
@@ -90,6 +96,10 @@
* FUSE_CAP_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
* FUSE_CAP_BIG_WRITES: filesystem can handle write size larger than 4kB
* FUSE_CAP_DONT_MASK: don't apply umask to file mode on create operations
+ * FUSE_CAP_SPLICE_WRITE: ability to use splice() to write to the fuse device
+ * FUSE_CAP_SPLICE_MOVE: ability to move data to the fuse device with splice()
+ * FUSE_CAP_SPLICE_READ: ability to use splice() to read from the fuse device
+ * FUSE_CAP_IOCTL_DIR: ioctl support on directories
*/
#define FUSE_CAP_ASYNC_READ (1 << 0)
#define FUSE_CAP_POSIX_LOCKS (1 << 1)
@@ -97,6 +107,11 @@
#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
#define FUSE_CAP_BIG_WRITES (1 << 5)
#define FUSE_CAP_DONT_MASK (1 << 6)
+#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+#define FUSE_CAP_SPLICE_READ (1 << 9)
+#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+#define FUSE_CAP_IOCTL_DIR (1 << 11)
/**
* Ioctl flags
@@ -104,12 +119,14 @@
* FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
* FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
* FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_DIR: is a directory
*
* FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
*/
#define FUSE_IOCTL_COMPAT (1 << 0)
#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
#define FUSE_IOCTL_RETRY (1 << 2)
+#define FUSE_IOCTL_DIR (1 << 4)
#define FUSE_IOCTL_MAX_IOV 256
@@ -157,9 +174,19 @@
unsigned want;
/**
+ * Maximum number of backgrounded requests
+ */
+ unsigned max_background;
+
+ /**
+ * Kernel congestion threshold parameter
+ */
+ unsigned congestion_threshold;
+
+ /**
* For future use.
*/
- unsigned reserved[25];
+ unsigned reserved[23];
};
struct fuse_session;
@@ -233,6 +260,186 @@
void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
/* ----------------------------------------------------------- *
+ * Data buffer *
+ * ----------------------------------------------------------- */
+
+/**
+ * Buffer flags
+ */
+enum fuse_buf_flags {
+ /**
+ * Buffer contains a file descriptor
+ *
+ * If this flag is set, the .fd field is valid, otherwise the
+ * .mem fields is valid.
+ */
+ FUSE_BUF_IS_FD = (1 << 1),
+
+ /**
+ * Seek on the file descriptor
+ *
+ * If this flag is set then the .pos field is valid and is
+ * used to seek to the given offset before performing
+ * operation on file descriptor.
+ */
+ FUSE_BUF_FD_SEEK = (1 << 2),
+
+ /**
+ * Retry operation on file descriptor
+ *
+ * If this flag is set then retry operation on file descriptor
+ * until .size bytes have been copied or an error or EOF is
+ * detected.
+ */
+ FUSE_BUF_FD_RETRY = (1 << 3),
+};
+
+/**
+ * Buffer copy flags
+ */
+enum fuse_buf_copy_flags {
+ /**
+ * Don't use splice(2)
+ *
+ * Always fall back to using read and write instead of
+ * splice(2) to copy data from one file descriptor to another.
+ *
+ * If this flag is not set, then only fall back if splice is
+ * unavailable.
+ */
+ FUSE_BUF_NO_SPLICE = (1 << 1),
+
+ /**
+ * Force splice
+ *
+ * Always use splice(2) to copy data from one file descriptor
+ * to another. If splice is not available, return -EINVAL.
+ */
+ FUSE_BUF_FORCE_SPLICE = (1 << 2),
+
+ /**
+ * Try to move data with splice.
+ *
+ * If splice is used, try to move pages from the source to the
+ * destination instead of copying. See documentation of
+ * SPLICE_F_MOVE in splice(2) man page.
+ */
+ FUSE_BUF_SPLICE_MOVE = (1 << 3),
+
+ /**
+ * Don't block on the pipe when copying data with splice
+ *
+ * Makes the operations on the pipe non-blocking (if the pipe
+ * is full or empty). See SPLICE_F_NONBLOCK in the splice(2)
+ * man page.
+ */
+ FUSE_BUF_SPLICE_NONBLOCK= (1 << 4),
+};
+
+/**
+ * Single data buffer
+ *
+ * Generic data buffer for I/O, extended attributes, etc... Data may
+ * be supplied as a memory pointer or as a file descriptor
+ */
+struct fuse_buf {
+ /**
+ * Size of data in bytes
+ */
+ size_t size;
+
+ /**
+ * Buffer flags
+ */
+ enum fuse_buf_flags flags;
+
+ /**
+ * Memory pointer
+ *
+ * Used unless FUSE_BUF_IS_FD flag is set.
+ */
+ void *mem;
+
+ /**
+ * File descriptor
+ *
+ * Used if FUSE_BUF_IS_FD flag is set.
+ */
+ int fd;
+
+ /**
+ * File position
+ *
+ * Used if FUSE_BUF_FD_SEEK flag is set.
+ */
+ off64_t pos;
+};
+
+/**
+ * Data buffer vector
+ *
+ * An array of data buffers, each containing a memory pointer or a
+ * file descriptor.
+ *
+ * Allocate dynamically to add more than one buffer.
+ */
+struct fuse_bufvec {
+ /**
+ * Number of buffers in the array
+ */
+ size_t count;
+
+ /**
+ * Index of current buffer within the array
+ */
+ size_t idx;
+
+ /**
+ * Current offset within the current buffer
+ */
+ size_t off;
+
+ /**
+ * Array of buffers
+ */
+ struct fuse_buf buf[1];
+};
+
+/* Initialize bufvec with a single buffer of given size */
+#define FUSE_BUFVEC_INIT(size__) \
+ ((struct fuse_bufvec) { \
+ /* .count= */ 1, \
+ /* .idx = */ 0, \
+ /* .off = */ 0, \
+ /* .buf = */ { /* [0] = */ { \
+ /* .size = */ (size__), \
+ /* .flags = */ (enum fuse_buf_flags) 0, \
+ /* .mem = */ NULL, \
+ /* .fd = */ -1, \
+ /* .pos = */ 0, \
+ } } \
+ } )
+
+/**
+ * Get total size of data in a fuse buffer vector
+ *
+ * @param bufv buffer vector
+ * @return size of data
+ */
+size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
+/**
+ * Copy data from one buffer vector to another
+ *
+ * @param dst destination buffer vector
+ * @param src source buffer vector
+ * @param flags flags controlling the copy
+ * @return actual number of bytes copied or -errno on error
+ */
+ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+ enum fuse_buf_copy_flags flags);
+
+/* ----------------------------------------------------------- *
* Signal handling *
* ----------------------------------------------------------- */
diff --git a/fuse/include/fuse_compat.h b/fuse/include/fuse_compat.h
index 95b7c78..d093238 100644
--- a/fuse/include/fuse_compat.h
+++ b/fuse/include/fuse_compat.h
@@ -65,7 +65,7 @@
void fuse_teardown_compat22(struct fuse *fuse, int fd, char *mountpoint);
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
#include <sys/statfs.h>
struct fuse_operations_compat22 {
@@ -198,4 +198,4 @@
void fuse_main_compat1(int argc, char *argv[],
const struct fuse_operations_compat1 *op);
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
diff --git a/fuse/include/fuse_kernel.h b/fuse/include/fuse_kernel.h
index bd73630..c632b58 100644
--- a/fuse/include/fuse_kernel.h
+++ b/fuse/include/fuse_kernel.h
@@ -56,6 +56,33 @@
* - add umask flag to input argument of open, mknod and mkdir
* - add notification messages for invalidation of inodes and
* directory entries
+ *
+ * 7.13
+ * - make max number of background requests and congestion threshold
+ * tunables
+ *
+ * 7.14
+ * - add splice support to fuse device
+ *
+ * 7.15
+ * - add store notify
+ * - add retrieve notify
+ *
+ * 7.16
+ * - add BATCH_FORGET request
+ * - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+ * fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+ * - add FUSE_IOCTL_32BIT flag
+ *
+ * 7.17
+ * - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+ *
+ * 7.18
+ * - add FUSE_IOCTL_DIR flag
+ * - add FUSE_NOTIFY_DELETE
+ *
+ * 7.19
+ * - add FUSE_FALLOCATE
*/
#ifndef _LINUX_FUSE_H
@@ -66,6 +93,7 @@
#define __s64 int64_t
#define __u32 uint32_t
#define __s32 int32_t
+#define __u16 uint16_t
/*
* Version negotiation:
@@ -91,7 +119,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 12
+#define FUSE_KERNEL_MINOR_VERSION 19
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -166,8 +194,10 @@
/**
* INIT request/reply flags
*
+ * FUSE_POSIX_LOCKS: remote locking for POSIX file locks
* FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
* FUSE_DONT_MASK: don't apply umask to file mode on create operations
+ * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks
*/
#define FUSE_ASYNC_READ (1 << 0)
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -176,6 +206,7 @@
#define FUSE_EXPORT_SUPPORT (1 << 4)
#define FUSE_BIG_WRITES (1 << 5)
#define FUSE_DONT_MASK (1 << 6)
+#define FUSE_FLOCK_LOCKS (1 << 10)
/**
* CUSE INIT request/reply flags
@@ -188,6 +219,7 @@
* Release flags
*/
#define FUSE_RELEASE_FLUSH (1 << 0)
+#define FUSE_RELEASE_FLOCK_UNLOCK (1 << 1)
/**
* Getattr flags
@@ -219,12 +251,16 @@
* FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
* FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
* FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_32BIT: 32bit ioctl
+ * FUSE_IOCTL_DIR: is a directory
*
* FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
*/
#define FUSE_IOCTL_COMPAT (1 << 0)
#define FUSE_IOCTL_UNRESTRICTED (1 << 1)
#define FUSE_IOCTL_RETRY (1 << 2)
+#define FUSE_IOCTL_32BIT (1 << 3)
+#define FUSE_IOCTL_DIR (1 << 4)
#define FUSE_IOCTL_MAX_IOV 256
@@ -274,6 +310,9 @@
FUSE_DESTROY = 38,
FUSE_IOCTL = 39,
FUSE_POLL = 40,
+ FUSE_NOTIFY_REPLY = 41,
+ FUSE_BATCH_FORGET = 42,
+ FUSE_FALLOCATE = 43,
/* CUSE specific operations */
CUSE_INIT = 4096,
@@ -283,6 +322,9 @@
FUSE_NOTIFY_POLL = 1,
FUSE_NOTIFY_INVAL_INODE = 2,
FUSE_NOTIFY_INVAL_ENTRY = 3,
+ FUSE_NOTIFY_STORE = 4,
+ FUSE_NOTIFY_RETRIEVE = 5,
+ FUSE_NOTIFY_DELETE = 6,
FUSE_NOTIFY_CODE_MAX,
};
@@ -306,6 +348,16 @@
__u64 nlookup;
};
+struct fuse_forget_one {
+ __u64 nodeid;
+ __u64 nlookup;
+};
+
+struct fuse_batch_forget_in {
+ __u32 count;
+ __u32 dummy;
+};
+
struct fuse_getattr_in {
__u32 getattr_flags;
__u32 dummy;
@@ -477,7 +529,8 @@
__u32 minor;
__u32 max_readahead;
__u32 flags;
- __u32 unused;
+ __u16 max_background;
+ __u16 congestion_threshold;
__u32 max_write;
};
@@ -525,6 +578,11 @@
__u32 out_size;
};
+struct fuse_ioctl_iovec {
+ __u64 base;
+ __u64 len;
+};
+
struct fuse_ioctl_out {
__s32 result;
__u32 flags;
@@ -548,6 +606,14 @@
__u64 kh;
};
+struct fuse_fallocate_in {
+ __u64 fh;
+ __u64 offset;
+ __u64 length;
+ __u32 mode;
+ __u32 padding;
+};
+
struct fuse_in_header {
__u32 len;
__u32 opcode;
@@ -570,7 +636,7 @@
__u64 off;
__u32 namelen;
__u32 type;
- char name[0];
+ char name[];
};
#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
@@ -590,4 +656,36 @@
__u32 padding;
};
+struct fuse_notify_delete_out {
+ __u64 parent;
+ __u64 child;
+ __u32 namelen;
+ __u32 padding;
+};
+
+struct fuse_notify_store_out {
+ __u64 nodeid;
+ __u64 offset;
+ __u32 size;
+ __u32 padding;
+};
+
+struct fuse_notify_retrieve_out {
+ __u64 notify_unique;
+ __u64 nodeid;
+ __u64 offset;
+ __u32 size;
+ __u32 padding;
+};
+
+/* Matches the size of fuse_write_in */
+struct fuse_notify_retrieve_in {
+ __u64 dummy1;
+ __u64 offset;
+ __u32 size;
+ __u32 dummy2;
+ __u64 dummy3;
+ __u64 dummy4;
+};
+
#endif /* _LINUX_FUSE_H */
diff --git a/fuse/include/fuse_lowlevel.h b/fuse/include/fuse_lowlevel.h
index f1cfeb9..36cf26d 100644
--- a/fuse/include/fuse_lowlevel.h
+++ b/fuse/include/fuse_lowlevel.h
@@ -77,9 +77,16 @@
/** Generation number for this entry.
*
- * The ino/generation pair should be unique for the filesystem's
- * lifetime. It must be non-zero, otherwise FUSE will treat it as an
- * error.
+ * If the file system will be exported over NFS, the
+ * ino/generation pairs need to be unique over the file
+ * system's lifetime (rather than just the mount time). So if
+ * the file system reuses an inode after it has been deleted,
+ * it must assign a new, previously unused generation number
+ * to the inode at the same time.
+ *
+ * The generation must be non-zero, otherwise FUSE will treat
+ * it as an error.
+ *
*/
unsigned long generation;
@@ -114,6 +121,11 @@
mode_t umask;
};
+struct fuse_forget_data {
+ uint64_t ino;
+ uint64_t nlookup;
+};
+
/* 'to_set' flags in setattr */
#define FUSE_SET_ATTR_MODE (1 << 0)
#define FUSE_SET_ATTR_UID (1 << 1)
@@ -188,18 +200,31 @@
/**
* Forget about an inode
*
- * The nlookup parameter indicates the number of lookups
- * previously performed on this inode.
+ * This function is called when the kernel removes an inode
+ * from its internal caches.
*
- * If the filesystem implements inode lifetimes, it is recommended
- * that inodes acquire a single reference on each lookup, and lose
- * nlookup references on each forget.
+ * The inode's lookup count increases by one for every call to
+ * fuse_reply_entry and fuse_reply_create. The nlookup parameter
+ * indicates by how much the lookup count should be decreased.
*
- * The filesystem may ignore forget calls, if the inodes don't
- * need to have a limited lifetime.
+ * Inodes with a non-zero lookup count may receive request from
+ * the kernel even after calls to unlink, rmdir or (when
+ * overwriting an existing file) rename. Filesystems must handle
+ * such requests properly and it is recommended to defer removal
+ * of the inode until the lookup count reaches zero. Calls to
+ * unlink, remdir or rename will be followed closely by forget
+ * unless the file or directory is open, in which case the
+ * kernel issues forget only after the release or releasedir
+ * calls.
*
- * On unmount it is not guaranteed, that all referenced inodes
- * will receive a forget message.
+ * Note that if a file system will be exported over NFS the
+ * inodes lifetime must extend even beyond forget. See the
+ * generation field in struct fuse_entry_param above.
+ *
+ * On unmount the lookup count for all inodes implicitly drops
+ * to zero. It is not guaranteed that the file system will
+ * receive corresponding forget messages for the affected
+ * inodes.
*
* Valid replies:
* fuse_reply_none
@@ -303,6 +328,11 @@
/**
* Remove a file
*
+ * If the file's inode's lookup count is non-zero, the file
+ * system is expected to postpone any removal of the inode
+ * until the lookup count reaches zero (see description of the
+ * forget function).
+ *
* Valid replies:
* fuse_reply_err
*
@@ -315,6 +345,11 @@
/**
* Remove a directory
*
+ * If the directory's inode's lookup count is non-zero, the
+ * file system is expected to postpone any removal of the
+ * inode until the lookup count reaches zero (see description
+ * of the forget function).
+ *
* Valid replies:
* fuse_reply_err
*
@@ -341,6 +376,12 @@
/** Rename a file
*
+ * If the target exists it should be atomically replaced. If
+ * the target's inode's lookup count is non-zero, the file
+ * system is expected to postpone any removal of the inode
+ * until the lookup count reaches zero (see description of the
+ * forget function).
+ *
* Valid replies:
* fuse_reply_err
*
@@ -412,6 +453,7 @@
* Valid replies:
* fuse_reply_buf
* fuse_reply_iov
+ * fuse_reply_data
* fuse_reply_err
*
* @param req request handle
@@ -561,6 +603,7 @@
*
* Valid replies:
* fuse_reply_buf
+ * fuse_reply_data
* fuse_reply_err
*
* @param req request handle
@@ -646,6 +689,7 @@
*
* Valid replies:
* fuse_reply_buf
+ * fuse_reply_data
* fuse_reply_xattr
* fuse_reply_err
*
@@ -672,6 +716,7 @@
*
* Valid replies:
* fuse_reply_buf
+ * fuse_reply_data
* fuse_reply_xattr
* fuse_reply_err
*
@@ -787,7 +832,7 @@
* @param req request handle
* @param ino the inode number
* @param fi file information
- * @param lock the region/type to test
+ * @param lock the region/type to set
* @param sleep locking operation may sleep
*/
void (*setlk) (fuse_req_t req, fuse_ino_t ino,
@@ -873,6 +918,104 @@
*/
void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
struct fuse_pollhandle *ph);
+
+ /**
+ * Write data made available in a buffer
+ *
+ * This is a more generic version of the ->write() method. If
+ * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the
+ * kernel supports splicing from the fuse device, then the
+ * data will be made available in pipe for supporting zero
+ * copy data transfer.
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_write
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param bufv buffer containing the data
+ * @param off offset to write to
+ * @param fi file information
+ */
+ void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_bufvec *bufv, off64_t off,
+ struct fuse_file_info *fi);
+
+ /**
+ * Callback function for the retrieve request
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_none
+ *
+ * @param req request handle
+ * @param cookie user data supplied to fuse_lowlevel_notify_retrieve()
+ * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve()
+ * @param offset the offset supplied to fuse_lowlevel_notify_retrieve()
+ * @param bufv the buffer containing the returned data
+ */
+ void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+ off64_t offset, struct fuse_bufvec *bufv);
+
+ /**
+ * Forget about multiple inodes
+ *
+ * See description of the forget function for more
+ * information.
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_none
+ *
+ * @param req request handle
+ */
+ void (*forget_multi) (fuse_req_t req, size_t count,
+ struct fuse_forget_data *forgets);
+
+ /**
+ * Acquire, modify or release a BSD file lock
+ *
+ * Note: if the locking methods are not implemented, the kernel
+ * will still allow file locking to work locally. Hence these are
+ * only interesting for network filesystems and similar.
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ * @param op the locking operation, see flock(2)
+ */
+ void (*flock) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, int op);
+
+ /**
+ * Allocate requested space. If this function returns success then
+ * subsequent writes to the specified range shall not fail due to the lack
+ * of free space on the file system storage media.
+ *
+ * Introduced in version 2.9
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param offset starting point for allocated region
+ * @param length size of allocated region
+ * @param mode determines the operation to be performed on the given range,
+ * see fallocate(2)
+ */
+ void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+ off64_t offset, off64_t length, struct fuse_file_info *fi);
};
/**
@@ -906,6 +1049,9 @@
* Possible requests:
* lookup, mknod, mkdir, symlink, link
*
+ * Side effects:
+ * increments the lookup count on success
+ *
* @param req request handle
* @param e the entry parameters
* @return zero for success, -errno for failure to send reply
@@ -921,6 +1067,9 @@
* Possible requests:
* create
*
+ * Side effects:
+ * increments the lookup count on success
+ *
* @param req request handle
* @param e the entry parameters
* @param fi file information
@@ -996,6 +1145,20 @@
int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
/**
+ * Reply with data copied/moved from buffer(s)
+ *
+ * Possible requests:
+ * read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags);
+
+/**
* Reply with data vector
*
* Possible requests:
@@ -1042,7 +1205,7 @@
* @param lock the lock information
* @return zero for success, -errno for failure to send reply
*/
-int fuse_reply_lock(fuse_req_t req, struct flock *lock);
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
/**
* Reply with block index
@@ -1181,6 +1344,75 @@
int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
const char *name, size_t namelen);
+/**
+ * Notify to invalidate parent attributes and delete the dentry matching
+ * parent/name if the dentry's inode number matches child (otherwise it
+ * will invalidate the matching dentry).
+ *
+ * @param ch the channel through which to send the notification
+ * @param parent inode number
+ * @param child inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_delete(struct fuse_chan *ch,
+ fuse_ino_t parent, fuse_ino_t child,
+ const char *name, size_t namelen);
+
+/**
+ * Store data to the kernel buffers
+ *
+ * Synchronously store data in the kernel buffers belonging to the
+ * given inode. The stored data is marked up-to-date (no read will be
+ * performed against it, unless it's invalidated or evicted from the
+ * cache).
+ *
+ * If the stored data overflows the current file size, then the size
+ * is extended, similarly to a write(2) on the filesystem.
+ *
+ * If this function returns an error, then the store wasn't fully
+ * completed, but it may have been partially completed.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param offset the starting offset into the file to store to
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
+ off64_t offset, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags);
+/**
+ * Retrieve data from the kernel buffers
+ *
+ * Retrieve data in the kernel buffers belonging to the given inode.
+ * If successful then the retrieve_reply() method will be called with
+ * the returned data.
+ *
+ * Only present pages are returned in the retrieve reply. Retrieving
+ * stops when it finds a non-present page and only data prior to that is
+ * returned.
+ *
+ * If this function returns an error, then the retrieve will not be
+ * completed and no reply will be sent.
+ *
+ * This function doesn't change the dirty state of pages in the kernel
+ * buffer. For dirty pages the write() method will be called
+ * regardless of having been retrieved previously.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param size the number of bytes to retrieve
+ * @param offset the starting offset into the file to retrieve from
+ * @param cookie user data to supply to the reply callback
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+ size_t size, off64_t offset, void *cookie);
+
+
/* ----------------------------------------------------------- *
* Utility functions *
* ----------------------------------------------------------- */
@@ -1377,6 +1609,34 @@
struct fuse_chan *ch);
/**
+ * Process a raw request supplied in a generic buffer
+ *
+ * This is a more generic version of fuse_session_process(). The
+ * fuse_buf may contain a memory buffer or a pipe file descriptor.
+ *
+ * @param se the session
+ * @param buf the fuse_buf containing the request
+ * @param ch channel on which the request was received
+ */
+void fuse_session_process_buf(struct fuse_session *se,
+ const struct fuse_buf *buf, struct fuse_chan *ch);
+
+/**
+ * Receive a raw request supplied in a generic buffer
+ *
+ * This is a more generic version of fuse_chan_recv(). The fuse_buf
+ * supplied to this function contains a suitably allocated memory
+ * buffer. This may be overwritten with a file descriptor buffer.
+ *
+ * @param se the session
+ * @param buf the fuse_buf to store the request in
+ * @param chp pointer to the channel
+ * @return the actual size of the raw request, or -errno on error
+ */
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan **chp);
+
+/**
* Destroy a session
*
* @param se the session
diff --git a/fuse/include/fuse_lowlevel_compat.h b/fuse/include/fuse_lowlevel_compat.h
index 3d2902d..78b7c2b 100644
--- a/fuse/include/fuse_lowlevel_compat.h
+++ b/fuse/include/fuse_lowlevel_compat.h
@@ -72,7 +72,7 @@
char *fuse_add_dirent(char *buf, const char *name, const struct stat *stbuf,
off64_t off);
-#ifndef __FreeBSD__
+#if !defined(__FreeBSD__) && !defined(__NetBSD__)
#include <sys/statfs.h>
@@ -139,7 +139,7 @@
const struct fuse_lowlevel_ops_compat *op,
size_t op_size, void *userdata);
-#endif /* __FreeBSD__ */
+#endif /* __FreeBSD__ || __NetBSD__ */
struct fuse_chan_ops_compat24 {
int (*receive)(struct fuse_chan *ch, char *buf, size_t size);
diff --git a/fuse/include/fuse_opt.h b/fuse/include/fuse_opt.h
index 8c08d77..add0a30 100644
--- a/fuse/include/fuse_opt.h
+++ b/fuse/include/fuse_opt.h
@@ -21,7 +21,7 @@
/**
* Option description
*
- * This structure describes a single option, and and action associated
+ * This structure describes a single option, and action associated
* with it, in case it matches.
*
* More than one such match may occur, in which case the action for
@@ -130,7 +130,7 @@
/**
* Key value passed to the processing function for all non-options
*
- * Non-options are the arguments beginning with a charater other than
+ * Non-options are the arguments beginning with a character other than
* '-' or all arguments after the special '--' option
*/
#define FUSE_OPT_KEY_NONOPT -2
@@ -161,7 +161,7 @@
*
* The 'arg' parameter will always contain the whole argument or
* option including the parameter if exists. A two-argument option
- * ("-x foo") is always converted to single arguemnt option of the
+ * ("-x foo") is always converted to single argument option of the
* form "-xfoo" before this function is called.
*
* Options of the form '-ofoo' are passed to this function without the
@@ -234,7 +234,7 @@
* argument vector
*
* Adds the argument to the N-th position. This is useful for adding
- * options at the beggining of the array which must not come after the
+ * options at the beginning of the array which must not come after the
* special '--' option.
*
* @param args is the structure containing the current argument list
diff --git a/fuse/mount.c b/fuse/mount.c
index 88ab597..af7218f 100644
--- a/fuse/mount.c
+++ b/fuse/mount.c
@@ -17,6 +17,7 @@
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
+#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/poll.h>
@@ -25,6 +26,20 @@
#include <sys/wait.h>
#include <sys/mount.h>
+#ifdef __NetBSD__
+#include <perfuse.h>
+
+#define MS_RDONLY MNT_RDONLY
+#define MS_NOSUID MNT_NOSUID
+#define MS_NODEV MNT_NODEV
+#define MS_NOEXEC MNT_NOEXEC
+#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+#define MS_NOATIME MNT_NOATIME
+
+
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#endif
+
#define FUSERMOUNT_PROG "fusermount"
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
@@ -54,6 +69,7 @@
int ishelp;
int flags;
int nonempty;
+ int auto_unmount;
int blkdev;
char *fsname;
char *subtype;
@@ -70,11 +86,13 @@
FUSE_MOUNT_OPT("allow_root", allow_root),
FUSE_MOUNT_OPT("nonempty", nonempty),
FUSE_MOUNT_OPT("blkdev", blkdev),
+ FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
FUSE_MOUNT_OPT("fsname=%s", fsname),
FUSE_MOUNT_OPT("subtype=%s", subtype),
FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
FUSE_OPT_KEY("allow_root", KEY_ALLOW_ROOT),
FUSE_OPT_KEY("nonempty", KEY_FUSERMOUNT_OPT),
+ FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
@@ -110,6 +128,7 @@
fprintf(stderr,
" -o allow_other allow access to other users\n"
" -o allow_root allow access to root\n"
+" -o auto_unmount auto unmount on process termination\n"
" -o nonempty allow mounts over non-empty file/dir\n"
" -o default_permissions enable permission checking by kernel\n"
" -o fsname=NAME set filesystem name\n"
@@ -120,7 +139,6 @@
}
#define FUSERMOUNT_DIR "/usr/bin"
-
static void exec_fusermount(const char *argv[])
{
execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv);
@@ -144,7 +162,7 @@
int on;
};
-static struct mount_flags mount_flags[] = {
+static const struct mount_flags mount_flags[] = {
{"rw", MS_RDONLY, 0},
{"ro", MS_RDONLY, 1},
{"suid", MS_NOSUID, 0},
@@ -157,7 +175,9 @@
{"sync", MS_SYNCHRONOUS, 1},
{"atime", MS_NOATIME, 0},
{"noatime", MS_NOATIME, 1},
+#ifndef __NetBSD__
{"dirsync", MS_DIRSYNC, 1},
+#endif
{NULL, 0, 0}
};
@@ -239,6 +259,7 @@
iov.iov_base = buf;
iov.iov_len = 1;
+ memset(&msg, 0, sizeof(msg));
msg.msg_name = 0;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
@@ -319,8 +340,8 @@
fuse_kern_unmount(mountpoint, -1);
}
-static int fuse_mount_fusermount(const char *mountpoint, const char *opts,
- int quiet)
+static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+ const char *opts, int quiet)
{
int fds[2], pid;
int res;
@@ -352,8 +373,10 @@
if (quiet) {
int fd = open("/dev/null", O_RDONLY);
- dup2(fd, 1);
- dup2(fd, 2);
+ if (fd != -1) {
+ dup2(fd, 1);
+ dup2(fd, 2);
+ }
}
argv[a++] = FUSERMOUNT_PROG;
@@ -376,15 +399,24 @@
close(fds[0]);
rv = receive_fd(fds[1]);
- close(fds[1]);
- waitpid(pid, NULL, 0); /* bury zombie */
+
+ if (!mo->auto_unmount) {
+ /* with auto_unmount option fusermount will not exit until
+ this socket is closed */
+ close(fds[1]);
+ waitpid(pid, NULL, 0); /* bury zombie */
+ }
return rv;
}
int fuse_mount_compat22(const char *mountpoint, const char *opts)
{
- return fuse_mount_fusermount(mountpoint, opts, 0);
+ struct mount_opts mo;
+ memset(&mo, 0, sizeof(mo));
+ mo.flags = MS_NOSUID | MS_NODEV;
+
+ return fuse_mount_fusermount(mountpoint, &mo, opts, 0);
}
static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
@@ -417,6 +449,12 @@
return -1;
}
+ if (mo->auto_unmount) {
+ /* Tell the caller to fallback to fusermount because
+ auto-unmount does not work otherwise. */
+ return -2;
+ }
+
fd = open(devname, O_RDWR);
if (fd == -1) {
if (errno == ENODEV || errno == ENOENT)
@@ -486,6 +524,8 @@
goto out_close;
}
+#ifndef __NetBSD__
+#ifndef IGNORE_MTAB
if (geteuid() == 0) {
char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
res = -1;
@@ -498,6 +538,8 @@
if (res == -1)
goto out_umount;
}
+#endif /* IGNORE_MTAB */
+#endif /* __NetBSD__ */
free(type);
free(source);
@@ -572,13 +614,13 @@
goto out;
}
- res = fuse_mount_fusermount(mountpoint, tmp_opts, 1);
+ res = fuse_mount_fusermount(mountpoint, &mo, tmp_opts, 1);
free(tmp_opts);
if (res == -1)
- res = fuse_mount_fusermount(mountpoint,
+ res = fuse_mount_fusermount(mountpoint, &mo,
mnt_opts, 0);
} else {
- res = fuse_mount_fusermount(mountpoint, mnt_opts, 0);
+ res = fuse_mount_fusermount(mountpoint, &mo, mnt_opts, 0);
}
}
out:
diff --git a/fuse/mount_util.c b/fuse/mount_util.c
index de1bd67..bfd801f 100644
--- a/fuse/mount_util.c
+++ b/fuse/mount_util.c
@@ -11,18 +11,22 @@
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
+#include <signal.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <mntent.h>
+#include <paths.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <sys/param.h>
-#include <paths.h>
-
+#ifdef __NetBSD__
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#define mtab_needs_update(mnt) 0
+#else
static int mtab_needs_update(const char *mnt)
{
int res;
@@ -45,80 +49,28 @@
if (errno == ENOENT)
return 0;
} else {
+ uid_t ruid;
+ int err;
+
if (S_ISLNK(stbuf.st_mode))
return 0;
+ ruid = getuid();
+ if (ruid != 0)
+ setreuid(0, -1);
+
res = access(_PATH_MOUNTED, W_OK);
- if (res == -1 && errno == EROFS)
+ err = (res == -1) ? errno : 0;
+ if (ruid != 0)
+ setreuid(ruid, -1);
+
+ if (err == EROFS)
return 0;
}
return 1;
}
-
-static int add_mount_legacy(const char *progname, const char *fsname,
- const char *mnt, const char *type, const char *opts)
-{
- int res;
- int status;
- sigset_t blockmask;
- sigset_t oldmask;
-
- sigemptyset(&blockmask);
- sigaddset(&blockmask, SIGCHLD);
- res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
- if (res == -1) {
- fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
- return -1;
- }
-
- res = fork();
- if (res == -1) {
- fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
- goto out_restore;
- }
- if (res == 0) {
- char templ[] = "/tmp/fusermountXXXXXX";
- char *tmp;
-
- sigprocmask(SIG_SETMASK, &oldmask, NULL);
- setuid(geteuid());
-
- /*
- * hide in a directory, where mount isn't able to resolve
- * fsname as a valid path
- */
- tmp = mkdtemp(templ);
- if (!tmp) {
- fprintf(stderr,
- "%s: failed to create temporary directory\n",
- progname);
- exit(1);
- }
- if (chdir(tmp)) {
- fprintf(stderr, "%s: failed to chdir to %s: %s\n",
- progname, tmp, strerror(errno));
- exit(1);
- }
- rmdir(tmp);
- execl("/bin/mount", "/bin/mount", "-i", "-f", "-t", type,
- "-o", opts, fsname, mnt, NULL);
- fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
- progname, strerror(errno));
- exit(1);
- }
- res = waitpid(res, &status, 0);
- if (res == -1)
- fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
-
- if (status != 0)
- res = -1;
-
- out_restore:
- sigprocmask(SIG_SETMASK, &oldmask, NULL);
-
- return res;
-}
+#endif /* __NetBSD__ */
static int add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts)
@@ -142,14 +94,6 @@
goto out_restore;
}
if (res == 0) {
- /*
- * Hide output, because old versions don't support
- * --no-canonicalize
- */
- int fd = open("/dev/null", O_RDONLY);
- dup2(fd, 1);
- dup2(fd, 2);
-
sigprocmask(SIG_SETMASK, &oldmask, NULL);
setuid(geteuid());
execl("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
@@ -174,16 +118,10 @@
int fuse_mnt_add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts)
{
- int res;
-
if (!mtab_needs_update(mnt))
return 0;
- res = add_mount(progname, fsname, mnt, type, opts);
- if (res == -1)
- res = add_mount_legacy(progname, fsname, mnt, type, opts);
-
- return res;
+ return add_mount(progname, fsname, mnt, type, opts);
}
static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
@@ -245,6 +183,55 @@
return exec_umount(progname, rel_mnt, lazy);
}
+static int remove_mount(const char *progname, const char *mnt)
+{
+ int res;
+ int status;
+ sigset_t blockmask;
+ sigset_t oldmask;
+
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGCHLD);
+ res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+ if (res == -1) {
+ fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+ return -1;
+ }
+
+ res = fork();
+ if (res == -1) {
+ fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+ goto out_restore;
+ }
+ if (res == 0) {
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ setuid(geteuid());
+ execl("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+ "--fake", mnt, NULL);
+ fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+ progname, strerror(errno));
+ exit(1);
+ }
+ res = waitpid(res, &status, 0);
+ if (res == -1)
+ fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+ if (status != 0)
+ res = -1;
+
+ out_restore:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ return res;
+}
+
+int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+{
+ if (!mtab_needs_update(mnt))
+ return 0;
+
+ return remove_mount(progname, mnt);
+}
+
char *fuse_mnt_resolve_path(const char *progname, const char *orig)
{
char buf[PATH_MAX];
diff --git a/fuse/mount_util.h b/fuse/mount_util.h
index 5c2fb8e..f89c115 100644
--- a/fuse/mount_util.h
+++ b/fuse/mount_util.h
@@ -10,6 +10,7 @@
int fuse_mnt_add_mount(const char *progname, const char *fsname,
const char *mnt, const char *type, const char *opts);
+int fuse_mnt_remove_mount(const char *progname, const char *mnt);
int fuse_mnt_umount(const char *progname, const char *abs_mnt,
const char *rel_mnt, int lazy);
char *fuse_mnt_resolve_path(const char *progname, const char *orig);
diff --git a/fuse/ulockmgr.c b/fuse/ulockmgr.c
index 4a049d5..f0523ae 100644
--- a/fuse/ulockmgr.c
+++ b/fuse/ulockmgr.c
@@ -400,6 +400,10 @@
if (cmd != F_GETLK && cmd != F_SETLK && cmd != F_SETLKW)
return -EINVAL;
+ if (lock->l_type != F_RDLCK && lock->l_type != F_WRLCK &&
+ lock->l_type != F_UNLCK)
+ return -EINVAL;
+
if (lock->l_whence != SEEK_SET && lock->l_whence != SEEK_CUR &&
lock->l_whence != SEEK_END)
return -EINVAL;