diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7ecfe95795cd..9d0ef5e18740 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -177,22 +177,6 @@ static int fuse_dentry_revalidate(struct dentry *entry, struct nameidata *nd) return 1; } -/* - * Check if there's already a hashed alias of this directory inode. - * If yes, then lookup and mkdir must not create a new alias. - */ -static int dir_alias(struct inode *inode) -{ - if (S_ISDIR(inode->i_mode)) { - struct dentry *alias = d_find_alias(inode); - if (alias) { - dput(alias); - return 1; - } - } - return 0; -} - static int invalid_nodeid(u64 nodeid) { return !nodeid || nodeid == FUSE_ROOT_ID; @@ -208,6 +192,24 @@ static int valid_mode(int m) S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m); } +/* + * Add a directory inode to a dentry, ensuring that no other dentry + * refers to this inode. Called with fc->inst_mutex. + */ +static int fuse_d_add_directory(struct dentry *entry, struct inode *inode) +{ + struct dentry *alias = d_find_alias(inode); + if (alias) { + /* This tries to shrink the subtree below alias */ + fuse_invalidate_entry(alias); + dput(alias); + if (!list_empty(&inode->i_dentry)) + return -EBUSY; + } + d_add(entry, inode); + return 0; +} + static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, struct nameidata *nd) { @@ -243,11 +245,17 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, if (err && err != -ENOENT) return ERR_PTR(err); - if (inode && dir_alias(inode)) { - iput(inode); - return ERR_PTR(-EIO); - } - d_add(entry, inode); + if (inode && S_ISDIR(inode->i_mode)) { + mutex_lock(&fc->inst_mutex); + err = fuse_d_add_directory(entry, inode); + mutex_unlock(&fc->inst_mutex); + if (err) { + iput(inode); + return ERR_PTR(err); + } + } else + d_add(entry, inode); + entry->d_op = &fuse_dentry_operations; if (!err) fuse_change_timeout(entry, &outarg); @@ -403,12 +411,22 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req, } fuse_put_request(fc, req); - if (dir_alias(inode)) { - iput(inode); - return -EIO; - } + if (S_ISDIR(inode->i_mode)) { + struct dentry *alias; + mutex_lock(&fc->inst_mutex); + alias = d_find_alias(inode); + if (alias) { + /* New directory must have moved since mkdir */ + mutex_unlock(&fc->inst_mutex); + dput(alias); + iput(inode); + return -EBUSY; + } + d_instantiate(entry, inode); + mutex_unlock(&fc->inst_mutex); + } else + d_instantiate(entry, inode); - d_instantiate(entry, inode); fuse_change_timeout(entry, &outarg); fuse_invalidate_attr(dir); return 0; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 69c7750d55b8..91edb8932d90 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -239,6 +239,9 @@ struct fuse_conn { /** Lock protecting accessess to members of this structure */ spinlock_t lock; + /** Mutex protecting against directory alias creation */ + struct mutex inst_mutex; + /** Refcount */ atomic_t count; diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 4ee8f72e6380..fc4203570370 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -379,6 +379,7 @@ static struct fuse_conn *new_conn(void) fc = kzalloc(sizeof(*fc), GFP_KERNEL); if (fc) { spin_lock_init(&fc->lock); + mutex_init(&fc->inst_mutex); atomic_set(&fc->count, 1); init_waitqueue_head(&fc->waitq); init_waitqueue_head(&fc->blocked_waitq); @@ -398,8 +399,10 @@ static struct fuse_conn *new_conn(void) void fuse_conn_put(struct fuse_conn *fc) { - if (atomic_dec_and_test(&fc->count)) + if (atomic_dec_and_test(&fc->count)) { + mutex_destroy(&fc->inst_mutex); kfree(fc); + } } struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)