In gfs2_lookup_by_inum, we must take the glock of a presumed inode
before we can determine if the block indeed still contains an inode,
then look up the inode.  When a lookup finds an inode that is being
freed, it usually waits until that inodes has gone before returning.
However, freeing the inode requires taking the inode glock, so we end up
deadlocking.

Fix that by changing gfs2_inode_lookup: instead of waiting for inodes
that are being freed, return the context necessary for waiting.  Then,
in gfs2_lookup_by_inum, drop the glock before waiting and retrying the
lookup.

Signed-off-by: Andreas Gruenbacher <agrue...@redhat.com>
---
 fs/gfs2/dir.c        |  2 +-
 fs/gfs2/inode.c      | 42 +++++++++++++++++++++++++-----------------
 fs/gfs2/inode.h      |  2 +-
 fs/gfs2/ops_fstype.c |  2 +-
 4 files changed, 28 insertions(+), 20 deletions(-)

diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c
index d4014af..eb54888 100644
--- a/fs/gfs2/dir.c
+++ b/fs/gfs2/dir.c
@@ -1660,7 +1660,7 @@ struct inode *gfs2_dir_search(struct inode *dir, const 
struct qstr *name,
                brelse(bh);
                if (fail_on_exist)
                        return ERR_PTR(-EEXIST);
-               inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino, 
0);
+               inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino, 
NULL);
                if (!IS_ERR(inode))
                        GFS2_I(inode)->i_rahead = rahead;
                return inode;
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index 1fa4799..fb7ccb0 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -85,30 +85,29 @@ struct inode *gfs2_ilookup(struct super_block *sb, u64 
no_addr)
 }
 
 static struct inode *gfs2_iget(struct super_block *sb, u64 no_addr,
-                              int non_block)
+                              struct gfs2_freeing_inode *freeing)
 {
-       struct gfs2_freeing_inode freeing;
-       struct gfs2_match match = {
-               .no_addr = no_addr,
-               .freeing = &freeing,
-       };
+       struct gfs2_freeing_inode wait_here;
+       struct gfs2_match match;
        struct inode *inode;
 
+       if (!freeing)
+               freeing = &wait_here;
+       match.no_addr = no_addr;
+       match.freeing = freeing;
        while (1) {
-               freeing.wq = NULL;
+               freeing->wq = NULL;
                inode = find_inode_nowait(sb, no_addr,
                                          gfs2_match_inode, &match);
                if (inode) {
                        wait_on_inode(inode);
                        return inode;
                }
-               if (freeing.wq) {
-                       if (non_block) {
-                               finish_wait(freeing.wq, &freeing.bit_wait.wait);
+               if (freeing->wq) {
+                       if (freeing != &wait_here)
                                return ERR_PTR(-EAGAIN);
-                       }
                        schedule();
-                       finish_wait(freeing.wq, &freeing.bit_wait.wait);
+                       finish_wait(freeing->wq, &freeing->bit_wait.wait);
                        continue;
                }
 
@@ -162,22 +161,23 @@ static void gfs2_set_iop(struct inode *inode)
 /**
  * gfs2_inode_lookup - Lookup an inode
  * @sb: The super block
- * @no_addr: The inode number
  * @type: The type of the inode
- * non_block: Can we block on inodes that are being freed?
+ * @no_addr: The inode number
+ * @freeing: Filled in when inode is being freed
  *
  * Returns: A VFS inode, or an error
  */
 
 struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned int type,
-                               u64 no_addr, u64 no_formal_ino, int non_block)
+                               u64 no_addr, u64 no_formal_ino,
+                               struct gfs2_freeing_inode *freeing)
 {
        struct inode *inode;
        struct gfs2_inode *ip;
        struct gfs2_glock *io_gl = NULL;
        int error;
 
-       inode = gfs2_iget(sb, no_addr, non_block);
+       inode = gfs2_iget(sb, no_addr, freeing);
        if (IS_ERR(inode))
                return inode;
        ip = GFS2_I(inode);
@@ -237,11 +237,13 @@ fail:
 struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
                                  u64 *no_formal_ino, unsigned int blktype)
 {
+       struct gfs2_freeing_inode freeing;
        struct super_block *sb = sdp->sd_vfs;
        struct gfs2_holder i_gh;
        struct inode *inode = NULL;
        int error;
 
+repeat:
        /* Must not read in block until block type is verified */
        error = gfs2_glock_nq_num(sdp, no_addr, &gfs2_inode_glops,
                                  LM_ST_EXCLUSIVE, GL_SKIP, &i_gh);
@@ -252,7 +254,13 @@ struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, 
u64 no_addr,
        if (error)
                goto fail;
 
-       inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0, 1);
+       inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0, &freeing);
+       if (inode == ERR_PTR(-EAGAIN)) {
+               gfs2_glock_dq_uninit(&i_gh);
+               schedule();
+               finish_wait(freeing.wq, &freeing.bit_wait.wait);
+               goto repeat;
+       }
        if (IS_ERR(inode))
                goto fail;
 
diff --git a/fs/gfs2/inode.h b/fs/gfs2/inode.h
index 4863513..f043601 100644
--- a/fs/gfs2/inode.h
+++ b/fs/gfs2/inode.h
@@ -100,7 +100,7 @@ struct gfs2_freeing_inode {
 
 extern struct inode *gfs2_inode_lookup(struct super_block *sb, unsigned type, 
                                       u64 no_addr, u64 no_formal_ino,
-                                      int non_block);
+                                      struct gfs2_freeing_inode *freeing);
 extern struct inode *gfs2_lookup_by_inum(struct gfs2_sbd *sdp, u64 no_addr,
                                         u64 *no_formal_ino,
                                         unsigned int blktype);
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index dbed9e2..cfc4158 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -454,7 +454,7 @@ static int gfs2_lookup_root(struct super_block *sb, struct 
dentry **dptr,
        struct dentry *dentry;
        struct inode *inode;
 
-       inode = gfs2_inode_lookup(sb, DT_DIR, no_addr, 0, 0);
+       inode = gfs2_inode_lookup(sb, DT_DIR, no_addr, 0, NULL);
        if (IS_ERR(inode)) {
                fs_err(sdp, "can't read in %s inode: %ld\n", name, 
PTR_ERR(inode));
                return PTR_ERR(inode);
-- 
2.5.5

Reply via email to