aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorMiklos Szeredi <miklos@szeredi.hu>2006-10-17 03:10:11 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2006-10-17 11:18:45 -0400
commitd2a85164aaa8d514ef5efbf5d05746e85dd13ddd (patch)
tree82a10eff007860a9bfc515d037cc2fea6c999bc0 /fs
parent265126ba9e1f8e217e61d1017c6609f76828aa7a (diff)
[PATCH] fuse: fix handling of moved directory
Fuse considered it an error (EIO) if lookup returned a directory inode, to which a dentry already refered. This is because directory aliases are not allowed. But in a network filesystem this could happen legitimately, if a directory is moved on a remote client. This patch attempts to relax the restriction by trying to first evict the offending alias from the cache. If this fails, it still returns an error (EBUSY). A rarer situation is if an mkdir races with an indenpendent lookup, which finds the newly created directory already moved. In this situation the mkdir should return success, but that would be incorrect, since the dentry cannot be instantiated, so return EBUSY. Previously checking for a directory alias and instantiation of the dentry weren't done atomically in lookup/mkdir, hence two such calls racing with each other could create aliased directories. To prevent this introduce a new per-connection mutex: fuse_conn->inst_mutex, which is taken for instantiations with a directory inode. Signed-off-by: Miklos Szeredi <miklos@szeredi.hu> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/fuse/dir.c70
-rw-r--r--fs/fuse/fuse_i.h3
-rw-r--r--fs/fuse/inode.c5
3 files changed, 51 insertions, 27 deletions
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)
177 return 1; 177 return 1;
178} 178}
179 179
180/*
181 * Check if there's already a hashed alias of this directory inode.
182 * If yes, then lookup and mkdir must not create a new alias.
183 */
184static int dir_alias(struct inode *inode)
185{
186 if (S_ISDIR(inode->i_mode)) {
187 struct dentry *alias = d_find_alias(inode);
188 if (alias) {
189 dput(alias);
190 return 1;
191 }
192 }
193 return 0;
194}
195
196static int invalid_nodeid(u64 nodeid) 180static int invalid_nodeid(u64 nodeid)
197{ 181{
198 return !nodeid || nodeid == FUSE_ROOT_ID; 182 return !nodeid || nodeid == FUSE_ROOT_ID;
@@ -208,6 +192,24 @@ static int valid_mode(int m)
208 S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m); 192 S_ISBLK(m) || S_ISFIFO(m) || S_ISSOCK(m);
209} 193}
210 194
195/*
196 * Add a directory inode to a dentry, ensuring that no other dentry
197 * refers to this inode. Called with fc->inst_mutex.
198 */
199static int fuse_d_add_directory(struct dentry *entry, struct inode *inode)
200{
201 struct dentry *alias = d_find_alias(inode);
202 if (alias) {
203 /* This tries to shrink the subtree below alias */
204 fuse_invalidate_entry(alias);
205 dput(alias);
206 if (!list_empty(&inode->i_dentry))
207 return -EBUSY;
208 }
209 d_add(entry, inode);
210 return 0;
211}
212
211static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry, 213static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
212 struct nameidata *nd) 214 struct nameidata *nd)
213{ 215{
@@ -243,11 +245,17 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
243 if (err && err != -ENOENT) 245 if (err && err != -ENOENT)
244 return ERR_PTR(err); 246 return ERR_PTR(err);
245 247
246 if (inode && dir_alias(inode)) { 248 if (inode && S_ISDIR(inode->i_mode)) {
247 iput(inode); 249 mutex_lock(&fc->inst_mutex);
248 return ERR_PTR(-EIO); 250 err = fuse_d_add_directory(entry, inode);
249 } 251 mutex_unlock(&fc->inst_mutex);
250 d_add(entry, inode); 252 if (err) {
253 iput(inode);
254 return ERR_PTR(err);
255 }
256 } else
257 d_add(entry, inode);
258
251 entry->d_op = &fuse_dentry_operations; 259 entry->d_op = &fuse_dentry_operations;
252 if (!err) 260 if (!err)
253 fuse_change_timeout(entry, &outarg); 261 fuse_change_timeout(entry, &outarg);
@@ -403,12 +411,22 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_req *req,
403 } 411 }
404 fuse_put_request(fc, req); 412 fuse_put_request(fc, req);
405 413
406 if (dir_alias(inode)) { 414 if (S_ISDIR(inode->i_mode)) {
407 iput(inode); 415 struct dentry *alias;
408 return -EIO; 416 mutex_lock(&fc->inst_mutex);
409 } 417 alias = d_find_alias(inode);
418 if (alias) {
419 /* New directory must have moved since mkdir */
420 mutex_unlock(&fc->inst_mutex);
421 dput(alias);
422 iput(inode);
423 return -EBUSY;
424 }
425 d_instantiate(entry, inode);
426 mutex_unlock(&fc->inst_mutex);
427 } else
428 d_instantiate(entry, inode);
410 429
411 d_instantiate(entry, inode);
412 fuse_change_timeout(entry, &outarg); 430 fuse_change_timeout(entry, &outarg);
413 fuse_invalidate_attr(dir); 431 fuse_invalidate_attr(dir);
414 return 0; 432 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 {
239 /** Lock protecting accessess to members of this structure */ 239 /** Lock protecting accessess to members of this structure */
240 spinlock_t lock; 240 spinlock_t lock;
241 241
242 /** Mutex protecting against directory alias creation */
243 struct mutex inst_mutex;
244
242 /** Refcount */ 245 /** Refcount */
243 atomic_t count; 246 atomic_t count;
244 247
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)
379 fc = kzalloc(sizeof(*fc), GFP_KERNEL); 379 fc = kzalloc(sizeof(*fc), GFP_KERNEL);
380 if (fc) { 380 if (fc) {
381 spin_lock_init(&fc->lock); 381 spin_lock_init(&fc->lock);
382 mutex_init(&fc->inst_mutex);
382 atomic_set(&fc->count, 1); 383 atomic_set(&fc->count, 1);
383 init_waitqueue_head(&fc->waitq); 384 init_waitqueue_head(&fc->waitq);
384 init_waitqueue_head(&fc->blocked_waitq); 385 init_waitqueue_head(&fc->blocked_waitq);
@@ -398,8 +399,10 @@ static struct fuse_conn *new_conn(void)
398 399
399void fuse_conn_put(struct fuse_conn *fc) 400void fuse_conn_put(struct fuse_conn *fc)
400{ 401{
401 if (atomic_dec_and_test(&fc->count)) 402 if (atomic_dec_and_test(&fc->count)) {
403 mutex_destroy(&fc->inst_mutex);
402 kfree(fc); 404 kfree(fc);
405 }
403} 406}
404 407
405struct fuse_conn *fuse_conn_get(struct fuse_conn *fc) 408struct fuse_conn *fuse_conn_get(struct fuse_conn *fc)