Merge tag 'gfs2-4.7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 24 Jul 2016 23:07:52 +0000 (16:07 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 24 Jul 2016 23:07:52 +0000 (16:07 -0700)
Pull gfs2 updates from Bob Peterson:
 "We've got ten patches this time, half of which are related to a
  plethora of nasty outcomes when inodes are transitioned from the
  unlinked state to the free state.  Small file systems are particularly
  vulnerable to these problems, and it can manifest as mainly hangs, but
  also file system corruption.  The patches have been tested for
  literally many weeks, with a very gruelling test, so I have a high
  level of confidence.

   - Andreas Gruenbacher wrote a series of five patches for various
     lockups during the transition of inodes from unlinked to free.

     The main patch is titled "Fix gfs2_lookup_by_inum lock inversion"
     and the other four are support and cleanup patches related to that.

   - Ben Marzinski contributed two patches with regard to a recreatable
     problem when gfs2 tries to write a page to a file that is being
     truncated, resulting in a BUG() in gfs2_remove_from_journal.

     Note that Ben had to export vfs function __block_write_full_page to
     get this to work properly.  It's been posted a long time and he
     talked to various VFS people about it, and nobody seemed to mind.

   - I contributed 3 patches:
       o The first one fixes a memory corruptor: a race in which one
         process can overwrite the gl_object pointer set by another
         process, causing kernel panic and other symptoms.
       o The second patch fixes another race that resulted in a
         false-positive BUG_ON.  This occurred when resource group
         reservations were freed by one process while another process
         was trying to grab a new reservation in the same resource
         group.
       o The third patch fixes a problem with doing journal replay when
         the journals are not all the same size"

* tag 'gfs2-4.7.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/gfs2/linux-gfs2:
  GFS2: Fix gfs2_replay_incr_blk for multiple journal sizes
  GFS2: Check rs_free with rd_rsspin protection
  gfs2: writeout truncated pages
  fs: export __block_write_full_page
  gfs2: Lock holder cleanup
  gfs2: Large-filesystem fix for 32-bit systems
  gfs2: Get rid of gfs2_ilookup
  gfs2: Fix gfs2_lookup_by_inum lock inversion
  gfs2: Initialize iopen glock holder for new inodes
  GFS2: don't set rgrp gl_object until it's inserted into rgrp tree

1  2 
fs/gfs2/dir.c
fs/gfs2/inode.c

diff --combined fs/gfs2/dir.c
@@@ -783,15 -783,12 +783,15 @@@ static int get_leaf_nr(struct gfs2_inod
                       u64 *leaf_out)
  {
        __be64 *hash;
 +      int error;
  
        hash = gfs2_dir_get_hash_table(dip);
 -      if (IS_ERR(hash))
 -              return PTR_ERR(hash);
 -      *leaf_out = be64_to_cpu(*(hash + index));
 -      return 0;
 +      error = PTR_ERR_OR_ZERO(hash);
 +
 +      if (!error)
 +              *leaf_out = be64_to_cpu(*(hash + index));
 +
 +      return error;
  }
  
  static int get_first_leaf(struct gfs2_inode *dip, u32 index,
        int error;
  
        error = get_leaf_nr(dip, index, &leaf_no);
 -      if (!IS_ERR_VALUE(error))
 +      if (!error)
                error = get_leaf(dip, leaf_no, bh_out);
  
        return error;
@@@ -1017,7 -1014,7 +1017,7 @@@ static int dir_split_leaf(struct inode 
  
        index = name->hash >> (32 - dip->i_depth);
        error = get_leaf_nr(dip, index, &leaf_no);
 -      if (IS_ERR_VALUE(error))
 +      if (error)
                return error;
  
        /*  Get the old leaf block  */
@@@ -1663,7 -1660,8 +1663,8 @@@ struct inode *gfs2_dir_search(struct in
                brelse(bh);
                if (fail_on_exist)
                        return ERR_PTR(-EEXIST);
-               inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino);
+               inode = gfs2_inode_lookup(dir->i_sb, dtype, addr, formal_ino,
+                                         GFS2_BLKST_FREE /* ignore */);
                if (!IS_ERR(inode))
                        GFS2_I(inode)->i_rahead = rahead;
                return inode;
diff --combined fs/gfs2/inode.c
  #include "super.h"
  #include "glops.h"
  
- struct inode *gfs2_ilookup(struct super_block *sb, u64 no_addr)
+ static int iget_test(struct inode *inode, void *opaque)
  {
-       return ilookup(sb, (unsigned long)no_addr);
+       u64 no_addr = *(u64 *)opaque;
+       return GFS2_I(inode)->i_no_addr == no_addr;
+ }
+ static int iget_set(struct inode *inode, void *opaque)
+ {
+       u64 no_addr = *(u64 *)opaque;
+       GFS2_I(inode)->i_no_addr = no_addr;
+       inode->i_ino = no_addr;
+       return 0;
+ }
+ static struct inode *gfs2_iget(struct super_block *sb, u64 no_addr)
+ {
+       struct inode *inode;
+ repeat:
+       inode = iget5_locked(sb, no_addr, iget_test, iget_set, &no_addr);
+       if (!inode)
+               return inode;
+       if (is_bad_inode(inode)) {
+               iput(inode);
+               goto repeat;
+       }
+       return inode;
  }
  
  /**
@@@ -78,26 -104,37 +104,37 @@@ static void gfs2_set_iop(struct inode *
  /**
   * gfs2_inode_lookup - Lookup an inode
   * @sb: The super block
-  * @no_addr: The inode number
   * @type: The type of the inode
+  * @no_addr: The inode number
+  * @no_formal_ino: The inode generation number
+  * @blktype: Requested block type (GFS2_BLKST_DINODE or GFS2_BLKST_UNLINKED;
+  *           GFS2_BLKST_FREE do indicate not to verify)
+  *
+  * If @type is DT_UNKNOWN, the inode type is fetched from disk.
+  *
+  * If @blktype is anything other than GFS2_BLKST_FREE (which is used as a
+  * placeholder because it doesn't otherwise make sense), the on-disk block type
+  * is verified to be @blktype.
   *
   * 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)
+                               u64 no_addr, u64 no_formal_ino,
+                               unsigned int blktype)
  {
        struct inode *inode;
        struct gfs2_inode *ip;
        struct gfs2_glock *io_gl = NULL;
+       struct gfs2_holder i_gh;
        int error;
  
-       inode = iget_locked(sb, (unsigned long)no_addr);
+       gfs2_holder_mark_uninitialized(&i_gh);
+       inode = gfs2_iget(sb, no_addr);
        if (!inode)
                return ERR_PTR(-ENOMEM);
  
        ip = GFS2_I(inode);
-       ip->i_no_addr = no_addr;
  
        if (inode->i_state & I_NEW) {
                struct gfs2_sbd *sdp = GFS2_SB(inode);
                if (unlikely(error))
                        goto fail_put;
  
+               if (type == DT_UNKNOWN || blktype != GFS2_BLKST_FREE) {
+                       /*
+                        * The GL_SKIP flag indicates to skip reading the inode
+                        * block.  We read the inode with gfs2_inode_refresh
+                        * after possibly checking the block type.
+                        */
+                       error = gfs2_glock_nq_init(ip->i_gl, LM_ST_EXCLUSIVE,
+                                                  GL_SKIP, &i_gh);
+                       if (error)
+                               goto fail_put;
+                       if (blktype != GFS2_BLKST_FREE) {
+                               error = gfs2_check_blk_type(sdp, no_addr,
+                                                           blktype);
+                               if (error)
+                                       goto fail_put;
+                       }
+               }
                set_bit(GIF_INVALID, &ip->i_flags);
                error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED, GL_EXACT, &ip->i_iopen_gh);
                if (unlikely(error))
-                       goto fail_iopen;
+                       goto fail_put;
  
                ip->i_iopen_gh.gh_gl->gl_object = ip;
                gfs2_glock_put(io_gl);
                unlock_new_inode(inode);
        }
  
+       if (gfs2_holder_initialized(&i_gh))
+               gfs2_glock_dq_uninit(&i_gh);
        return inode;
  
  fail_refresh:
        ip->i_iopen_gh.gh_gl->gl_object = NULL;
        gfs2_glock_dq_wait(&ip->i_iopen_gh);
        gfs2_holder_uninit(&ip->i_iopen_gh);
- fail_iopen:
+ fail_put:
        if (io_gl)
                gfs2_glock_put(io_gl);
- fail_put:
+       if (gfs2_holder_initialized(&i_gh))
+               gfs2_glock_dq_uninit(&i_gh);
        ip->i_gl->gl_object = NULL;
  fail:
        iget_failed(inode);
@@@ -155,23 -214,12 +214,12 @@@ struct inode *gfs2_lookup_by_inum(struc
                                  u64 *no_formal_ino, unsigned int blktype)
  {
        struct super_block *sb = sdp->sd_vfs;
-       struct gfs2_holder i_gh;
-       struct inode *inode = NULL;
+       struct inode *inode;
        int error;
  
-       /* 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);
-       if (error)
-               return ERR_PTR(error);
-       error = gfs2_check_blk_type(sdp, no_addr, blktype);
-       if (error)
-               goto fail;
-       inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0);
+       inode = gfs2_inode_lookup(sb, DT_UNKNOWN, no_addr, 0, blktype);
        if (IS_ERR(inode))
-               goto fail;
+               return inode;
  
        /* Two extra checks for NFS only */
        if (no_formal_ino) {
                error = -EIO;
                if (GFS2_I(inode)->i_diskflags & GFS2_DIF_SYSTEM)
                        goto fail_iput;
-               error = 0;
        }
+       return inode;
  
- fail:
-       gfs2_glock_dq_uninit(&i_gh);
-       return error ? ERR_PTR(error) : inode;
  fail_iput:
        iput(inode);
-       goto fail;
+       return ERR_PTR(error);
  }
  
  
@@@ -236,8 -280,8 +280,8 @@@ struct inode *gfs2_lookupi(struct inod
        struct gfs2_holder d_gh;
        int error = 0;
        struct inode *inode = NULL;
-       int unlock = 0;
  
+       gfs2_holder_mark_uninitialized(&d_gh);
        if (!name->len || name->len > GFS2_FNAMESIZE)
                return ERR_PTR(-ENAMETOOLONG);
  
                error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh);
                if (error)
                        return ERR_PTR(error);
-               unlock = 1;
        }
  
        if (!is_root) {
        if (IS_ERR(inode))
                error = PTR_ERR(inode);
  out:
-       if (unlock)
+       if (gfs2_holder_initialized(&d_gh))
                gfs2_glock_dq_uninit(&d_gh);
        if (error == -ENOENT)
                return NULL;
@@@ -1189,7 -1232,7 +1232,7 @@@ static int gfs2_atomic_open(struct inod
        struct dentry *d;
        bool excl = !!(flags & O_EXCL);
  
 -      if (!d_unhashed(dentry))
 +      if (!d_in_lookup(dentry))
                goto skip_lookup;
  
        d = __gfs2_lookup(dir, dentry, file, opened);
@@@ -1309,7 -1352,7 +1352,7 @@@ static int gfs2_rename(struct inode *od
        struct gfs2_inode *ip = GFS2_I(d_inode(odentry));
        struct gfs2_inode *nip = NULL;
        struct gfs2_sbd *sdp = GFS2_SB(odir);
-       struct gfs2_holder ghs[5], r_gh = { .gh_gl = NULL, };
+       struct gfs2_holder ghs[5], r_gh;
        struct gfs2_rgrpd *nrgd;
        unsigned int num_gh;
        int dir_rename = 0;
        unsigned int x;
        int error;
  
+       gfs2_holder_mark_uninitialized(&r_gh);
        if (d_really_is_positive(ndentry)) {
                nip = GFS2_I(d_inode(ndentry));
                if (ip == nip)
@@@ -1506,7 -1550,7 +1550,7 @@@ out_gunlock
                gfs2_holder_uninit(ghs + x);
        }
  out_gunlock_r:
-       if (r_gh.gh_gl)
+       if (gfs2_holder_initialized(&r_gh))
                gfs2_glock_dq_uninit(&r_gh);
  out:
        return error;
@@@ -1532,13 -1576,14 +1576,14 @@@ static int gfs2_exchange(struct inode *
        struct gfs2_inode *oip = GFS2_I(odentry->d_inode);
        struct gfs2_inode *nip = GFS2_I(ndentry->d_inode);
        struct gfs2_sbd *sdp = GFS2_SB(odir);
-       struct gfs2_holder ghs[5], r_gh = { .gh_gl = NULL, };
+       struct gfs2_holder ghs[5], r_gh;
        unsigned int num_gh;
        unsigned int x;
        umode_t old_mode = oip->i_inode.i_mode;
        umode_t new_mode = nip->i_inode.i_mode;
        int error;
  
+       gfs2_holder_mark_uninitialized(&r_gh);
        error = gfs2_rindex_update(sdp);
        if (error)
                return error;
@@@ -1646,7 -1691,7 +1691,7 @@@ out_gunlock
                gfs2_holder_uninit(ghs + x);
        }
  out_gunlock_r:
-       if (r_gh.gh_gl)
+       if (gfs2_holder_initialized(&r_gh))
                gfs2_glock_dq_uninit(&r_gh);
  out:
        return error;
@@@ -1743,9 -1788,8 +1788,8 @@@ int gfs2_permission(struct inode *inode
        struct gfs2_inode *ip;
        struct gfs2_holder i_gh;
        int error;
-       int unlock = 0;
  
+       gfs2_holder_mark_uninitialized(&i_gh);
        ip = GFS2_I(inode);
        if (gfs2_glock_is_locked_by_me(ip->i_gl) == NULL) {
                if (mask & MAY_NOT_BLOCK)
                error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &i_gh);
                if (error)
                        return error;
-               unlock = 1;
        }
  
        if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode))
                error = -EACCES;
        else
                error = generic_permission(inode, mask);
-       if (unlock)
+       if (gfs2_holder_initialized(&i_gh))
                gfs2_glock_dq_uninit(&i_gh);
  
        return error;
@@@ -1932,17 -1975,16 +1975,16 @@@ static int gfs2_getattr(struct vfsmoun
        struct gfs2_inode *ip = GFS2_I(inode);
        struct gfs2_holder gh;
        int error;
-       int unlock = 0;
  
+       gfs2_holder_mark_uninitialized(&gh);
        if (gfs2_glock_is_locked_by_me(ip->i_gl) == NULL) {
                error = gfs2_glock_nq_init(ip->i_gl, LM_ST_SHARED, LM_FLAG_ANY, &gh);
                if (error)
                        return error;
-               unlock = 1;
        }
  
        generic_fillattr(inode, stat);
-       if (unlock)
+       if (gfs2_holder_initialized(&gh))
                gfs2_glock_dq_uninit(&gh);
  
        return 0;