diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2013-02-04 18:32:38 +0100 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2013-02-04 18:32:38 +0100 |
commit | d8118645566010155f62b5203db4172a9a567000 (patch) | |
tree | 933de84234464d24d3ab840654e9e347453deb34 | |
parent | 07b4946b5bee7f60d533b8b21c1eea16dbb86917 (diff) | |
parent | eb385fbe931838ffd17c523cde4303059c1c8964 (diff) |
Merge branch 'fuse_2_9_bugfix'
-rw-r--r-- | ChangeLog | 30 | ||||
-rw-r--r-- | configure.ac (renamed from configure.in) | 10 | ||||
-rw-r--r-- | lib/buffer.c | 1 | ||||
-rw-r--r-- | lib/fuse.c | 292 | ||||
-rw-r--r-- | lib/fuse_lowlevel.c | 1 |
5 files changed, 213 insertions, 121 deletions
@@ -1,3 +1,33 @@ +2013-02-04 Miklos Szeredi <miklos@szeredi.hu> + + * libfuse: fix crash in unlock_path(). Patch by Ratna Manoj + + * libfuse: fix the 'remember' option. The lru list was not + initialized for the "/" path. This resulted in remove_node_lru() + crashing on LOOKUP-DOTDOT. Patch by Madan Valluri + + * libfuse: configure: detect new util-linux + + * libfuse: Use AC_CONFIG_HEADERS instead of AM_CONFIG_HEADER. + Patch by Anatol Pomozov + + * libfuse: rename ./configure.in to ./configure.ac. Patch by + Anatol Pomozov + +2012-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Released 2.9.2 + +2012-10-01 Miklos Szeredi <miklos@szeredi.hu> + + * Fix deadlock in libfuse. Running "svn update" on a fuse + filesystem could deadlock because of a bug in the way the paths + are locked. Reported by Kazuaki Anami + +2012-08-23 Miklos Szeredi <miklos@szeredi.hu> + + * Fix missing config.h in buffer.c. Reported by Matthew Gabeler-Lee + 2012-08-14 Miklos Szeredi <miklos@szeredi.hu> * Not unhashing the name in forget (commit on 2011-12-09) broke diff --git a/configure.in b/configure.ac index 95d7d2d..274ac70 100644 --- a/configure.in +++ b/configure.ac @@ -4,7 +4,7 @@ AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_TARGET AM_INIT_AUTOMAKE m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)]) -AM_CONFIG_HEADER(include/config.h) +AC_CONFIG_HEADERS(include/config.h) AC_PROG_LIBTOOL AC_PROG_CC @@ -102,8 +102,12 @@ if test "$arch" = linux -a "$cross_compiling" != "yes"; then AC_MSG_RESULT([yes]) else firstline=`umount --fake --no-canonicalize 2>&1 | head -1` - AC_MSG_RESULT([$firstline]) - util_linux_ok=no + if test "$firstline" = 'umount: only root can use "--fake" option'; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([$firstline]) + util_linux_ok=no + fi fi fi diff --git a/lib/buffer.c b/lib/buffer.c index 65b0dea..17a595c 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -8,6 +8,7 @@ #define _GNU_SOURCE +#include "config.h" #include "fuse_i.h" #include "fuse_lowlevel.h" #include <string.h> @@ -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" @@ -20,6 +21,7 @@ #include <string.h> #include <stdlib.h> #include <stddef.h> +#include <stdbool.h> #include <unistd.h> #include <time.h> #include <fcntl.h> @@ -87,8 +89,20 @@ struct fusemod_so { }; 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 { @@ -129,7 +143,6 @@ struct fuse { int intr_installed; struct fuse_fs *fs; int utime_omit_ok; - int curr_ticket; struct lock_queue_element *lockq; int pagesize; struct list_head partial_slabs; @@ -163,10 +176,12 @@ struct node { 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; @@ -893,48 +908,28 @@ static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name) } 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; - } -} - -static void release_tickets(struct fuse *f, fuse_ino_t nodeid, - struct node *wnode, int ticket) -{ - struct node *node; - - if (wnode) { - if (wnode->ticket != ticket) - return; - - wnode->ticket = 0; - } - - for (node = get_node(f, nodeid); - node->nodeid != FUSE_ROOT_ID; node = node->parent) { - if (node->ticket != ticket) - return; - node->ticket = 0; + 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; @@ -961,18 +956,16 @@ static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, } 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; goto out_free; } - wnode->treelock = -1; - wnode->ticket = 0; + wnode->treelock = TREELOCK_WRITE; } } @@ -987,14 +980,12 @@ static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, if (s == NULL) goto out_unlock; - if (ticket) { + if (need_lock) { err = -EAGAIN; - if (node->treelock == -1 || - (node->ticket && node->ticket != ticket)) + if (node->treelock < 0) goto out_unlock; node->treelock++; - node->ticket = 0; } } @@ -1010,40 +1001,97 @@ static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name, 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: - if (ticket && err != -EAGAIN) - release_tickets(f, nodeid, wnode, ticket); - 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); + qe->first_locked = false; + } + if (qe->second_locked) { + wnode = qe->wnode2 ? *qe->wnode2 : NULL; + unlock_path(f, qe->nodeid2, wnode, NULL); + qe->second_locked = false; + } } -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; @@ -1058,56 +1106,58 @@ static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid, } } -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); @@ -1149,22 +1199,19 @@ static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, 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); + err = try_get_path(f, nodeid2, name2, path2, wnode2, true); if (err) { struct node *wn1 = wnode1 ? *wnode1 : NULL; - unlock_path(f, nodeid1, wn1, NULL, ticket); + unlock_path(f, nodeid1, wn1, NULL); free(*path1); - if (ticket && err != -EAGAIN) - release_tickets(f, nodeid1, wn1, ticket); } } return err; @@ -1176,27 +1223,27 @@ static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, 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); @@ -1207,8 +1254,9 @@ static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid, 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); } @@ -1224,9 +1272,9 @@ static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2, 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); @@ -1245,15 +1293,19 @@ static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup) * 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); - do { - wait_on_path(f, &qe, node->nodeid, NULL, 0); - wake_up_next(&qe); + debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false); + queue_path(f, &qe); + do { + 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); @@ -2211,7 +2263,7 @@ static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname, newnode = lookup_node(f, dir, newname); } while(newnode); - res = 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 (res) break; @@ -4487,6 +4539,10 @@ struct fuse *fuse_new(struct fuse_chan *ch, struct fuse_args *args, fprintf(stderr, "fuse: memory allocation failed\n"); goto out_free_id_table; } + if (lru_enabled(f)) { + struct node_lru *lnode = node_lru(root); + init_list_head(&lnode->lru); + } strcpy(root->inline_name, "/"); root->name = root->inline_name; @@ -4545,7 +4601,7 @@ void fuse_destroy(struct fuse *f) 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); } diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 4178d9a..b4ea0e3 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -8,6 +8,7 @@ #define _GNU_SOURCE +#include "config.h" #include "fuse_i.h" #include "fuse_kernel.h" #include "fuse_opt.h" |