From 34aad20bc3fff3ce4e3fa8cb1d0265a1df5cded5 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 5 Jul 2019 17:38:35 +0200 Subject: [PATCH 01/15] gfs2: gfs2_iomap_begin cleanup Following commit d0a22a4b03b8 ("gfs2: Fix iomap write page reclaim deadlock"), gfs2_iomap_begin and gfs2_iomap_begin_write can be further cleaned up and the split between those two functions can be improved. With suggestions from Christoph Hellwig. Signed-off-by: Andreas Gruenbacher Reviewed-by: Bob Peterson --- fs/gfs2/bmap.c | 116 +++++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 4f8b5fd6c81f..907b79483050 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1065,54 +1065,38 @@ static int gfs2_iomap_begin_write(struct inode *inode, loff_t pos, { struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); - unsigned int data_blocks = 0, ind_blocks = 0, rblocks; - bool unstuff, alloc_required; + bool unstuff; int ret; - ret = gfs2_write_lock(inode); - if (ret) - return ret; - unstuff = gfs2_is_stuffed(ip) && pos + length > gfs2_max_stuffed_size(ip); - ret = gfs2_iomap_get(inode, pos, length, flags, iomap, mp); - if (ret) - goto out_unlock; + if (unstuff || iomap->type == IOMAP_HOLE) { + unsigned int data_blocks, ind_blocks; + struct gfs2_alloc_parms ap = {}; + unsigned int rblocks; + struct gfs2_trans *tr; - alloc_required = unstuff || iomap->type == IOMAP_HOLE; - - if (alloc_required || gfs2_is_jdata(ip)) gfs2_write_calc_reserv(ip, iomap->length, &data_blocks, &ind_blocks); - - if (alloc_required) { - struct gfs2_alloc_parms ap = { - .target = data_blocks + ind_blocks - }; - + ap.target = data_blocks + ind_blocks; ret = gfs2_quota_lock_check(ip, &ap); if (ret) - goto out_unlock; + return ret; ret = gfs2_inplace_reserve(ip, &ap); if (ret) goto out_qunlock; - } - rblocks = RES_DINODE + ind_blocks; - if (gfs2_is_jdata(ip)) - rblocks += data_blocks; - if (ind_blocks || data_blocks) - rblocks += RES_STATFS + RES_QUOTA; - if (inode == sdp->sd_rindex) - rblocks += 2 * RES_STATFS; - if (alloc_required) + rblocks = RES_DINODE + ind_blocks; + if (gfs2_is_jdata(ip)) + rblocks += data_blocks; + if (ind_blocks || data_blocks) + rblocks += RES_STATFS + RES_QUOTA; + if (inode == sdp->sd_rindex) + rblocks += 2 * RES_STATFS; rblocks += gfs2_rg_blocks(ip, data_blocks + ind_blocks); - if (unstuff || iomap->type == IOMAP_HOLE) { - struct gfs2_trans *tr; - ret = gfs2_trans_begin(sdp, rblocks, iomap->length >> inode->i_blkbits); if (ret) @@ -1153,16 +1137,17 @@ static int gfs2_iomap_begin_write(struct inode *inode, loff_t pos, out_trans_end: gfs2_trans_end(sdp); out_trans_fail: - if (alloc_required) - gfs2_inplace_release(ip); + gfs2_inplace_release(ip); out_qunlock: - if (alloc_required) - gfs2_quota_unlock(ip); -out_unlock: - gfs2_write_unlock(inode); + gfs2_quota_unlock(ip); return ret; } +static inline bool gfs2_iomap_need_write_lock(unsigned flags) +{ + return (flags & IOMAP_WRITE) && !(flags & IOMAP_DIRECT); +} + static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, unsigned flags, struct iomap *iomap) { @@ -1173,20 +1158,39 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, iomap->flags |= IOMAP_F_BUFFER_HEAD; trace_gfs2_iomap_start(ip, pos, length, flags); - if ((flags & IOMAP_WRITE) && !(flags & IOMAP_DIRECT)) { - ret = gfs2_iomap_begin_write(inode, pos, length, flags, iomap, &mp); - } else { - ret = gfs2_iomap_get(inode, pos, length, flags, iomap, &mp); - - /* - * Silently fall back to buffered I/O for stuffed files or if - * we've hot a hole (see gfs2_file_direct_write). - */ - if ((flags & IOMAP_WRITE) && (flags & IOMAP_DIRECT) && - iomap->type != IOMAP_MAPPED) - ret = -ENOTBLK; + if (gfs2_iomap_need_write_lock(flags)) { + ret = gfs2_write_lock(inode); + if (ret) + goto out; } + + ret = gfs2_iomap_get(inode, pos, length, flags, iomap, &mp); + if (ret) + goto out_unlock; + + switch(flags & IOMAP_WRITE) { + case IOMAP_WRITE: + if (flags & IOMAP_DIRECT) { + /* + * Silently fall back to buffered I/O for stuffed files + * or if we've got a hole (see gfs2_file_direct_write). + */ + if (iomap->type != IOMAP_MAPPED) + ret = -ENOTBLK; + goto out_unlock; + } + break; + default: + goto out_unlock; + } + + ret = gfs2_iomap_begin_write(inode, pos, length, flags, iomap, &mp); + +out_unlock: + if (ret && gfs2_iomap_need_write_lock(flags)) + gfs2_write_unlock(inode); release_metapath(&mp); +out: trace_gfs2_iomap_end(ip, iomap, ret); return ret; } @@ -1197,8 +1201,14 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); - if ((flags & (IOMAP_WRITE | IOMAP_DIRECT)) != IOMAP_WRITE) - goto out; + switch (flags & IOMAP_WRITE) { + case IOMAP_WRITE: + if (flags & IOMAP_DIRECT) + return 0; + break; + default: + return 0; + } if (!gfs2_is_stuffed(ip)) gfs2_ordered_add_inode(ip); @@ -1231,8 +1241,8 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, set_bit(GLF_DIRTY, &ip->i_gl->gl_flags); out_unlock: - gfs2_write_unlock(inode); -out: + if (gfs2_iomap_need_write_lock(flags)) + gfs2_write_unlock(inode); return 0; } From 72d36d0529c6d9ee8b950c819508b1e344d8cc4f Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 12 Jul 2019 15:57:52 +0200 Subject: [PATCH 02/15] gfs2: Add support for IOMAP_ZERO Add support for the IOMAP_ZERO iomap operation so that iomap_zero_range will work as expected. In the IOMAP_ZERO case, the caller of iomap_zero_range is responsible for taking an exclusive glock on the inode, so we need no additional locking in gfs2_iomap_begin. Signed-off-by: Andreas Gruenbacher Reviewed-by: Bob Peterson --- fs/gfs2/bmap.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 907b79483050..8431c14c3ee1 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1168,7 +1168,7 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, if (ret) goto out_unlock; - switch(flags & IOMAP_WRITE) { + switch(flags & (IOMAP_WRITE | IOMAP_ZERO)) { case IOMAP_WRITE: if (flags & IOMAP_DIRECT) { /* @@ -1180,6 +1180,10 @@ static int gfs2_iomap_begin(struct inode *inode, loff_t pos, loff_t length, goto out_unlock; } break; + case IOMAP_ZERO: + if (iomap->type == IOMAP_HOLE) + goto out_unlock; + break; default: goto out_unlock; } @@ -1201,11 +1205,15 @@ static int gfs2_iomap_end(struct inode *inode, loff_t pos, loff_t length, struct gfs2_inode *ip = GFS2_I(inode); struct gfs2_sbd *sdp = GFS2_SB(inode); - switch (flags & IOMAP_WRITE) { + switch (flags & (IOMAP_WRITE | IOMAP_ZERO)) { case IOMAP_WRITE: if (flags & IOMAP_DIRECT) return 0; break; + case IOMAP_ZERO: + if (iomap->type == IOMAP_HOLE) + return 0; + break; default: return 0; } From 2257e468a63b6d35a77e884ef032c54f9be65c92 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Mon, 1 Jul 2019 23:54:37 +0200 Subject: [PATCH 03/15] gfs2: implement gfs2_block_zero_range using iomap_zero_range iomap handles all the nitty-gritty details of zeroing a file range for us, so use the proper helper. Signed-off-by: Christoph Hellwig Signed-off-by: Andreas Gruenbacher Reviewed-by: Bob Peterson --- fs/gfs2/bmap.c | 68 +------------------------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 8431c14c3ee1..2043a728f281 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1348,76 +1348,10 @@ int gfs2_extent_map(struct inode *inode, u64 lblock, int *new, u64 *dblock, unsi return ret; } -/** - * gfs2_block_zero_range - Deal with zeroing out data - * - * This is partly borrowed from ext3. - */ static int gfs2_block_zero_range(struct inode *inode, loff_t from, unsigned int length) { - struct address_space *mapping = inode->i_mapping; - struct gfs2_inode *ip = GFS2_I(inode); - unsigned long index = from >> PAGE_SHIFT; - unsigned offset = from & (PAGE_SIZE-1); - unsigned blocksize, iblock, pos; - struct buffer_head *bh; - struct page *page; - int err; - - page = find_or_create_page(mapping, index, GFP_NOFS); - if (!page) - return 0; - - blocksize = inode->i_sb->s_blocksize; - iblock = index << (PAGE_SHIFT - inode->i_sb->s_blocksize_bits); - - if (!page_has_buffers(page)) - create_empty_buffers(page, blocksize, 0); - - /* Find the buffer that contains "offset" */ - bh = page_buffers(page); - pos = blocksize; - while (offset >= pos) { - bh = bh->b_this_page; - iblock++; - pos += blocksize; - } - - err = 0; - - if (!buffer_mapped(bh)) { - gfs2_block_map(inode, iblock, bh, 0); - /* unmapped? It's a hole - nothing to do */ - if (!buffer_mapped(bh)) - goto unlock; - } - - /* Ok, it's mapped. Make sure it's up-to-date */ - if (PageUptodate(page)) - set_buffer_uptodate(bh); - - if (!buffer_uptodate(bh)) { - err = -EIO; - ll_rw_block(REQ_OP_READ, 0, 1, &bh); - wait_on_buffer(bh); - /* Uhhuh. Read error. Complain and punt. */ - if (!buffer_uptodate(bh)) - goto unlock; - err = 0; - } - - if (gfs2_is_jdata(ip)) - gfs2_trans_add_data(ip->i_gl, bh); - else - gfs2_ordered_add_inode(ip); - - zero_user(page, offset, length); - mark_buffer_dirty(bh); -unlock: - unlock_page(page); - put_page(page); - return err; + return iomap_zero_range(inode, from, length, NULL, &gfs2_iomap_ops); } #define GFS2_JTRUNC_REVOKES 8192 From d40312598d534c17c17f41c2bb7ce9541a5f786e Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Wed, 24 Jul 2019 13:05:38 +0200 Subject: [PATCH 04/15] gfs2: Minor gfs2_alloc_inode cleanup In gfs2_alloc_inode, when kmem_cache_alloc cannot allocate a new object, return NULL immediately. The code currently relies on the fact that i_inode is the first member in struct gfs2_inode and so ip and &ip->i_inode evaluate to the same address, but that isn't immediately obvious. Signed-off-by: Andreas Gruenbacher Reviewed-by: Bob Peterson --- fs/gfs2/super.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c index 0acc5834f653..644c70ae09f7 100644 --- a/fs/gfs2/super.c +++ b/fs/gfs2/super.c @@ -1722,13 +1722,13 @@ static struct inode *gfs2_alloc_inode(struct super_block *sb) struct gfs2_inode *ip; ip = kmem_cache_alloc(gfs2_inode_cachep, GFP_KERNEL); - if (ip) { - ip->i_flags = 0; - ip->i_gl = NULL; - memset(&ip->i_res, 0, sizeof(ip->i_res)); - RB_CLEAR_NODE(&ip->i_res.rs_node); - ip->i_rahead = 0; - } + if (!ip) + return NULL; + ip->i_flags = 0; + ip->i_gl = NULL; + memset(&ip->i_res, 0, sizeof(ip->i_res)); + RB_CLEAR_NODE(&ip->i_res.rs_node); + ip->i_rahead = 0; return &ip->i_inode; } From 0a6a4abc84668d102c8f0380c9bcb93eb87fa4b6 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 8 Aug 2019 19:29:54 +0100 Subject: [PATCH 05/15] gfs2: Always mark inode dirty in fallocate When allocating space with fallocate, always update the file timestamps and mark the inode dirty, no matter if the FALLOC_FL_KEEP_SIZE flag is set or not. The inode needs to be marked dirty so that a subsequent fsync will pick it up and any new allocations will make it to disk. Filesystems like xfs and ext4 always update the timestamps, so make gfs2 behave the same way. Fixes xfstest generic/483. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/file.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index 52fa1ef8400b..99e2c8cd5eaa 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -1065,11 +1065,10 @@ static long __gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t gfs2_quota_unlock(ip); } - if (!(mode & FALLOC_FL_KEEP_SIZE) && (pos + count) > inode->i_size) { + if (!(mode & FALLOC_FL_KEEP_SIZE) && (pos + count) > inode->i_size) i_size_write(inode, pos + count); - file_update_time(file); - mark_inode_dirty(inode); - } + file_update_time(file); + mark_inode_dirty(inode); if ((file->f_flags & O_DSYNC) || IS_SYNC(file->f_mapping->host)) return vfs_fsync_range(file, pos, pos + count - 1, From 8c5ca11710b67b5f76b08d4bb25a576e3513a678 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Tue, 6 Aug 2019 13:52:21 -0400 Subject: [PATCH 06/15] gfs2: untangle the logic in gfs2_drevalidate Before this patch, function gfs2_drevalidate was a horrific tangle of unreadable labels, cases and goto statements. This patch tries to simplify the logic and make it more readable. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/dentry.c | 47 ++++++++++++----------------------------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/fs/gfs2/dentry.c b/fs/gfs2/dentry.c index a7bb76e9a82b..2e215e8c3c88 100644 --- a/fs/gfs2/dentry.c +++ b/fs/gfs2/dentry.c @@ -38,7 +38,7 @@ static int gfs2_drevalidate(struct dentry *dentry, unsigned int flags) struct inode *inode; struct gfs2_holder d_gh; struct gfs2_inode *ip = NULL; - int error; + int error, valid = 0; int had_lock = 0; if (flags & LOOKUP_RCU) @@ -51,53 +51,30 @@ static int gfs2_drevalidate(struct dentry *dentry, unsigned int flags) if (inode) { if (is_bad_inode(inode)) - goto invalid; + goto out; ip = GFS2_I(inode); } - if (sdp->sd_lockstruct.ls_ops->lm_mount == NULL) - goto valid; + if (sdp->sd_lockstruct.ls_ops->lm_mount == NULL) { + valid = 1; + goto out; + } had_lock = (gfs2_glock_is_locked_by_me(dip->i_gl) != NULL); if (!had_lock) { error = gfs2_glock_nq_init(dip->i_gl, LM_ST_SHARED, 0, &d_gh); if (error) - goto fail; - } - - error = gfs2_dir_check(d_inode(parent), &dentry->d_name, ip); - switch (error) { - case 0: - if (!inode) - goto invalid_gunlock; - break; - case -ENOENT: - if (!inode) - goto valid_gunlock; - goto invalid_gunlock; - default: - goto fail_gunlock; + goto out; } -valid_gunlock: + error = gfs2_dir_check(d_inode(parent), &dentry->d_name, ip); + valid = inode ? !error : (error == -ENOENT); + if (!had_lock) gfs2_glock_dq_uninit(&d_gh); -valid: +out: dput(parent); - return 1; - -invalid_gunlock: - if (!had_lock) - gfs2_glock_dq_uninit(&d_gh); -invalid: - dput(parent); - return 0; - -fail_gunlock: - gfs2_glock_dq_uninit(&d_gh); -fail: - dput(parent); - return 0; + return valid; } static int gfs2_dhash(const struct dentry *dentry, struct qstr *str) From 98fb057487a806303f93db3e0f746525ea487c59 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Tue, 13 Aug 2019 09:25:15 -0400 Subject: [PATCH 07/15] gfs2: Fix possible fs name overflows This patch fixes three places in which temporary character buffers could overflow due to the addition of the file system id from patch 3792ce973f07. Thanks to Dan Carpenter for pointing it out. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/glock.c | 2 +- fs/gfs2/rgrp.c | 2 +- fs/gfs2/util.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index e23fb8b7b020..a27dbd3dec01 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -1788,8 +1788,8 @@ void gfs2_dump_glock(struct seq_file *seq, struct gfs2_glock *gl, bool fsid) unsigned long long dtime; const struct gfs2_holder *gh; char gflags_buf[32]; - char fs_id_buf[GFS2_FSNAME_LEN + 3 * sizeof(int) + 2]; struct gfs2_sbd *sdp = gl->gl_name.ln_sbd; + char fs_id_buf[sizeof(sdp->sd_fsname) + 7]; memset(fs_id_buf, 0, sizeof(fs_id_buf)); if (fsid && sdp) /* safety precaution */ diff --git a/fs/gfs2/rgrp.c b/fs/gfs2/rgrp.c index 49ac0a5e74ea..2466bb44a23c 100644 --- a/fs/gfs2/rgrp.c +++ b/fs/gfs2/rgrp.c @@ -2285,7 +2285,7 @@ void gfs2_rgrp_dump(struct seq_file *seq, struct gfs2_glock *gl, static void gfs2_rgrp_error(struct gfs2_rgrpd *rgd) { struct gfs2_sbd *sdp = rgd->rd_sbd; - char fs_id_buf[GFS2_FSNAME_LEN + 3 * sizeof(int) + 2]; + char fs_id_buf[sizeof(sdp->sd_fsname) + 7]; fs_warn(sdp, "rgrp %llu has an error, marking it readonly until umount\n", (unsigned long long)rgd->rd_addr); diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c index 83f6c582773a..c45159133d8e 100644 --- a/fs/gfs2/util.c +++ b/fs/gfs2/util.c @@ -178,7 +178,7 @@ int gfs2_consist_rgrpd_i(struct gfs2_rgrpd *rgd, int cluster_wide, const char *function, char *file, unsigned int line) { struct gfs2_sbd *sdp = rgd->rd_sbd; - char fs_id_buf[GFS2_FSNAME_LEN + 3 * sizeof(int) + 2]; + char fs_id_buf[sizeof(sdp->sd_fsname) + 7]; int rv; sprintf(fs_id_buf, "fsid=%s: ", sdp->sd_fsname); From 8f0daef5f797c13d7f9bd248190933932b14df36 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Thu, 22 Aug 2019 18:07:09 +0200 Subject: [PATCH 08/15] gfs2: Fix recovery slot bumping Get rid of the assumption that the number of slots can at most increase by RECOVER_SIZE_INC (16) in set_recover_size. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/lock_dlm.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/lock_dlm.c b/fs/gfs2/lock_dlm.c index 4361804646d8..7c7197343ee2 100644 --- a/fs/gfs2/lock_dlm.c +++ b/fs/gfs2/lock_dlm.c @@ -1035,12 +1035,12 @@ static int set_recover_size(struct gfs2_sbd *sdp, struct dlm_slot *slots, } old_size = ls->ls_recover_size; - - if (old_size >= max_jid + 1) + new_size = old_size; + while (new_size < max_jid + 1) + new_size += RECOVER_SIZE_INC; + if (new_size == old_size) return 0; - new_size = old_size + RECOVER_SIZE_INC; - submit = kcalloc(new_size, sizeof(uint32_t), GFP_NOFS); result = kcalloc(new_size, sizeof(uint32_t), GFP_NOFS); if (!submit || !result) { From 45eb05042d5667408d101a8c056f70a8e8ac27f6 Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Mon, 2 Sep 2019 17:31:06 +0100 Subject: [PATCH 09/15] gfs2: Minor PAGE_SIZE arithmetic cleanups Replace divisions by PAGE_SIZE with shifts by PAGE_SHIFT and similar. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/aops.c | 4 ++-- fs/gfs2/file.c | 2 +- fs/gfs2/quota.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/aops.c b/fs/gfs2/aops.c index f42048cc5454..b9fe975d7625 100644 --- a/fs/gfs2/aops.c +++ b/fs/gfs2/aops.c @@ -243,7 +243,7 @@ static int gfs2_write_jdata_pagevec(struct address_space *mapping, { struct inode *inode = mapping->host; struct gfs2_sbd *sdp = GFS2_SB(inode); - unsigned nrblocks = nr_pages * (PAGE_SIZE/inode->i_sb->s_blocksize); + unsigned nrblocks = nr_pages * (PAGE_SIZE >> inode->i_blkbits); int i; int ret; @@ -552,7 +552,7 @@ int gfs2_internal_read(struct gfs2_inode *ip, char *buf, loff_t *pos, unsigned size) { struct address_space *mapping = ip->i_inode.i_mapping; - unsigned long index = *pos / PAGE_SIZE; + unsigned long index = *pos >> PAGE_SHIFT; unsigned offset = *pos & (PAGE_SIZE - 1); unsigned copied = 0; unsigned amt; diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index 99e2c8cd5eaa..997b326247e2 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -1049,7 +1049,7 @@ static long __gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t rblocks += data_blocks ? data_blocks : 1; error = gfs2_trans_begin(sdp, rblocks, - PAGE_SIZE/sdp->sd_sb.sb_bsize); + PAGE_SIZE >> inode->i_blkbits); if (error) goto out_trans_fail; diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c index 69c4b77f127b..7c016a082aa6 100644 --- a/fs/gfs2/quota.c +++ b/fs/gfs2/quota.c @@ -774,7 +774,7 @@ static int gfs2_write_disk_quota(struct gfs2_inode *ip, struct gfs2_quota *qp, nbytes = sizeof(struct gfs2_quota); pg_beg = loc >> PAGE_SHIFT; - pg_off = loc % PAGE_SIZE; + pg_off = offset_in_page(loc); /* If the quota straddles a page boundary, split the write in two */ if ((pg_off + nbytes) > PAGE_SIZE) { From bccaef90738581038ee609e946f36812bd6fb1b3 Mon Sep 17 00:00:00 2001 From: Markus Elfring Date: Tue, 3 Sep 2019 15:10:05 +0200 Subject: [PATCH 10/15] gfs2: Delete an unnecessary check before brelse() The brelse() function tests whether its argument is NULL and then returns immediately. Thus the test around the call is not needed. This issue was detected by using the Coccinelle software. [The same applies to brelse() in gfs2_dir_no_add (which Coccinelle apparently missed), so fix that as well.] Signed-off-by: Markus Elfring Signed-off-by: Andreas Gruenbacher --- fs/gfs2/dir.c | 3 +-- fs/gfs2/dir.h | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/fs/gfs2/dir.c b/fs/gfs2/dir.c index 6f35d19eec25..eb9c0578978f 100644 --- a/fs/gfs2/dir.c +++ b/fs/gfs2/dir.c @@ -1463,8 +1463,7 @@ static int gfs2_dir_read_leaf(struct inode *inode, struct dir_context *ctx, sort_offset : entries, copied); out_free: for(i = 0; i < leaf; i++) - if (larr[i]) - brelse(larr[i]); + brelse(larr[i]); kvfree(larr); out: return error; diff --git a/fs/gfs2/dir.h b/fs/gfs2/dir.h index 0ac2dc8564df..5b76480c17c9 100644 --- a/fs/gfs2/dir.h +++ b/fs/gfs2/dir.h @@ -32,8 +32,7 @@ extern int gfs2_dir_add(struct inode *inode, const struct qstr *filename, const struct gfs2_inode *ip, struct gfs2_diradd *da); static inline void gfs2_dir_no_add(struct gfs2_diradd *da) { - if (da->bh) - brelse(da->bh); + brelse(da->bh); da->bh = NULL; } extern int gfs2_dir_del(struct gfs2_inode *dip, const struct dentry *dentry); From bc74aaefdd538bb2756d74518c177378e6bd589f Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Fri, 30 Aug 2019 12:31:00 -0500 Subject: [PATCH 11/15] gfs2: separate holder for rgrps in gfs2_rename Before this patch, gfs2_rename added a holder for the rgrp glock to its array of holders, ghs. There's nothing wrong with that, but this patch separates it into a separate holder. This is done to ensure it's always locked last as per the proper glock lock ordering, and also to pave the way for a future patch in which we will lock the non-rgrp glocks asynchronously. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/inode.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 2e2a8a2fb51d..50eeb15c6f4f 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1348,7 +1348,7 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, 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; + struct gfs2_holder ghs[4], r_gh, rd_gh; struct gfs2_rgrpd *nrgd; unsigned int num_gh; int dir_rename = 0; @@ -1357,6 +1357,7 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, int error; gfs2_holder_mark_uninitialized(&r_gh); + gfs2_holder_mark_uninitialized(&rd_gh); if (d_really_is_positive(ndentry)) { nip = GFS2_I(d_inode(ndentry)); if (ip == nip) @@ -1398,13 +1399,6 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, if (nip) { gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); num_gh++; - /* grab the resource lock for unlink flag twiddling - * this is the case of the target file already existing - * so we unlink before doing the rename - */ - nrgd = gfs2_blk2rgrpd(sdp, nip->i_no_addr, 1); - if (nrgd) - gfs2_holder_init(nrgd->rd_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh++); } for (x = 0; x < num_gh; x++) { @@ -1413,6 +1407,22 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, goto out_gunlock; } + if (nip) { + /* Grab the resource group glock for unlink flag twiddling. + * This is the case where the target dinode already exists + * so we unlink before doing the rename. + */ + nrgd = gfs2_blk2rgrpd(sdp, nip->i_no_addr, 1); + if (!nrgd) { + error = -ENOENT; + goto out_gunlock; + } + error = gfs2_glock_nq_init(nrgd->rd_gl, LM_ST_EXCLUSIVE, 0, + &rd_gh); + if (error) + goto out_gunlock; + } + error = -ENOENT; if (ip->i_inode.i_nlink == 0) goto out_gunlock; @@ -1541,6 +1551,9 @@ out_gunlock_q: gfs2_quota_unlock(ndip); out_gunlock: gfs2_dir_no_add(&da); + if (gfs2_holder_initialized(&rd_gh)) + gfs2_glock_dq_uninit(&rd_gh); + while (x--) { gfs2_glock_dq(ghs + x); gfs2_holder_uninit(ghs + x); From 01123cf17cfa7c8c30109bdcf2f913f1e63ff97b Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 30 Aug 2019 12:31:01 -0500 Subject: [PATCH 12/15] gfs2: create function gfs2_glock_update_hold_time This patch moves the code that updates glock minimum hold time to a separate function. This will be called by a future patch. Signed-off-by: Andreas Gruenbacher Signed-off-by: Bob Peterson --- fs/gfs2/glock.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index a27dbd3dec01..661350989e98 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -931,6 +931,17 @@ void gfs2_holder_uninit(struct gfs2_holder *gh) gh->gh_ip = 0; } +static void gfs2_glock_update_hold_time(struct gfs2_glock *gl, + unsigned long start_time) +{ + /* Have we waited longer that a second? */ + if (time_after(jiffies, start_time + HZ)) { + /* Lengthen the minimum hold time. */ + gl->gl_hold_time = min(gl->gl_hold_time + GL_GLOCK_HOLD_INCR, + GL_GLOCK_MAX_HOLD); + } +} + /** * gfs2_glock_wait - wait on a glock acquisition * @gh: the glock holder @@ -940,15 +951,11 @@ void gfs2_holder_uninit(struct gfs2_holder *gh) int gfs2_glock_wait(struct gfs2_holder *gh) { - unsigned long time1 = jiffies; + unsigned long start_time = jiffies; might_sleep(); wait_on_bit(&gh->gh_iflags, HIF_WAIT, TASK_UNINTERRUPTIBLE); - if (time_after(jiffies, time1 + HZ)) /* have we waited > a second? */ - /* Lengthen the minimum hold time. */ - gh->gh_gl->gl_hold_time = min(gh->gh_gl->gl_hold_time + - GL_GLOCK_HOLD_INCR, - GL_GLOCK_MAX_HOLD); + gfs2_glock_update_hold_time(gh->gh_gl, start_time); return gh->gh_error; } From ad26967b9afa7faee22c3b79370cb5d9ab553493 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Fri, 30 Aug 2019 12:31:02 -0500 Subject: [PATCH 13/15] gfs2: Use async glocks for rename Because s_vfs_rename_mutex is not cluster-wide, multiple nodes can reverse the roles of which directories are "old" and which are "new" for the purposes of rename. This can cause deadlocks where two nodes end up waiting for each other. There can be several layers of directory dependencies across many nodes. This patch fixes the problem by acquiring all gfs2_rename's inode glocks asychronously and waiting for all glocks to be acquired. That way all inodes are locked regardless of the order. The timeout value for multiple asynchronous glocks is calculated to be the total of the individual wait times for each glock times two. Since gfs2_exchange is very similar to gfs2_rename, both functions are patched in the same way. A new async glock wait queue, sd_async_glock_wait, keeps a list of waiters for these events. If gfs2's holder_wake function detects an async holder, it wakes up any waiters for the event. The waiter only tests whether any of its requests are still pending. Since the glocks are sent to dlm asychronously, the wait function needs to check to see which glocks, if any, were granted. If a glock is granted by dlm (and therefore held), its minimum hold time is checked and adjusted as necessary, as other glock grants do. If the event times out, all glocks held thus far must be dequeued to resolve any existing deadlocks. Then, if there are any outstanding locking requests, we need to loop around and wait for dlm to respond to those requests too. After we release all requests, we return -ESTALE to the caller (vfs rename) which loops around and retries the request. Node1 Node2 --------- --------- 1. Enqueue A Enqueue B 2. Enqueue B Enqueue A 3. A granted 6. B granted 7. Wait for B 8. Wait for A 9. A times out (since Node 1 holds A) 10. Dequeue B (since it was granted) 11. Wait for all requests from DLM 12. B Granted (since Node2 released it in step 10) 13. Rename 14. Dequeue A 15. DLM Grants A 16. Dequeue A (due to the timeout and since we no longer have B held for our task). 17. Dequeue B 18. Return -ESTALE to vfs 19. VFS retries the operation, goto step 1. This release-all-locks / acquire-all-locks may slow rename / exchange down as both nodes struggle in the same way and do the same thing. However, this will only happen when there is contention for the same inodes, which ought to be rare. Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/glock.c | 94 +++++++++++++++++++++++++++++++++++++++++++- fs/gfs2/glock.h | 6 +++ fs/gfs2/incore.h | 1 + fs/gfs2/inode.c | 34 ++++++++++------ fs/gfs2/ops_fstype.c | 1 + 5 files changed, 123 insertions(+), 13 deletions(-) diff --git a/fs/gfs2/glock.c b/fs/gfs2/glock.c index 661350989e98..0290a22ebccf 100644 --- a/fs/gfs2/glock.c +++ b/fs/gfs2/glock.c @@ -305,6 +305,11 @@ static void gfs2_holder_wake(struct gfs2_holder *gh) clear_bit(HIF_WAIT, &gh->gh_iflags); smp_mb__after_atomic(); wake_up_bit(&gh->gh_iflags, HIF_WAIT); + if (gh->gh_flags & GL_ASYNC) { + struct gfs2_sbd *sdp = gh->gh_gl->gl_name.ln_sbd; + + wake_up(&sdp->sd_async_glock_wait); + } } /** @@ -959,6 +964,91 @@ int gfs2_glock_wait(struct gfs2_holder *gh) return gh->gh_error; } +static int glocks_pending(unsigned int num_gh, struct gfs2_holder *ghs) +{ + int i; + + for (i = 0; i < num_gh; i++) + if (test_bit(HIF_WAIT, &ghs[i].gh_iflags)) + return 1; + return 0; +} + +/** + * gfs2_glock_async_wait - wait on multiple asynchronous glock acquisitions + * @num_gh: the number of holders in the array + * @ghs: the glock holder array + * + * Returns: 0 on success, meaning all glocks have been granted and are held. + * -ESTALE if the request timed out, meaning all glocks were released, + * and the caller should retry the operation. + */ + +int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs) +{ + struct gfs2_sbd *sdp = ghs[0].gh_gl->gl_name.ln_sbd; + int i, ret = 0, timeout = 0; + unsigned long start_time = jiffies; + bool keep_waiting; + + might_sleep(); + /* + * Total up the (minimum hold time * 2) of all glocks and use that to + * determine the max amount of time we should wait. + */ + for (i = 0; i < num_gh; i++) + timeout += ghs[i].gh_gl->gl_hold_time << 1; + +wait_for_dlm: + if (!wait_event_timeout(sdp->sd_async_glock_wait, + !glocks_pending(num_gh, ghs), timeout)) + ret = -ESTALE; /* request timed out. */ + + /* + * If dlm granted all our requests, we need to adjust the glock + * minimum hold time values according to how long we waited. + * + * If our request timed out, we need to repeatedly release any held + * glocks we acquired thus far to allow dlm to acquire the remaining + * glocks without deadlocking. We cannot currently cancel outstanding + * glock acquisitions. + * + * The HIF_WAIT bit tells us which requests still need a response from + * dlm. + * + * If dlm sent us any errors, we return the first error we find. + */ + keep_waiting = false; + for (i = 0; i < num_gh; i++) { + /* Skip holders we have already dequeued below. */ + if (!gfs2_holder_queued(&ghs[i])) + continue; + /* Skip holders with a pending DLM response. */ + if (test_bit(HIF_WAIT, &ghs[i].gh_iflags)) { + keep_waiting = true; + continue; + } + + if (test_bit(HIF_HOLDER, &ghs[i].gh_iflags)) { + if (ret == -ESTALE) + gfs2_glock_dq(&ghs[i]); + else + gfs2_glock_update_hold_time(ghs[i].gh_gl, + start_time); + } + if (!ret) + ret = ghs[i].gh_error; + } + + if (keep_waiting) + goto wait_for_dlm; + + /* + * At this point, we've either acquired all locks or released them all. + */ + return ret; +} + /** * handle_callback - process a demote request * @gl: the glock @@ -1025,9 +1115,9 @@ __acquires(&gl->gl_lockref.lock) struct gfs2_holder *gh2; int try_futile = 0; - BUG_ON(gh->gh_owner_pid == NULL); + GLOCK_BUG_ON(gl, gh->gh_owner_pid == NULL); if (test_and_set_bit(HIF_WAIT, &gh->gh_iflags)) - BUG(); + GLOCK_BUG_ON(gl, true); if (gh->gh_flags & (LM_FLAG_TRY | LM_FLAG_TRY_1CB)) { if (test_bit(GLF_LOCK, &gl->gl_flags)) diff --git a/fs/gfs2/glock.h b/fs/gfs2/glock.h index e4e0bed5257c..b8adaf80e4c5 100644 --- a/fs/gfs2/glock.h +++ b/fs/gfs2/glock.h @@ -190,6 +190,7 @@ extern void gfs2_holder_uninit(struct gfs2_holder *gh); extern int gfs2_glock_nq(struct gfs2_holder *gh); extern int gfs2_glock_poll(struct gfs2_holder *gh); extern int gfs2_glock_wait(struct gfs2_holder *gh); +extern int gfs2_glock_async_wait(unsigned int num_gh, struct gfs2_holder *ghs); extern void gfs2_glock_dq(struct gfs2_holder *gh); extern void gfs2_glock_dq_wait(struct gfs2_holder *gh); extern void gfs2_glock_dq_uninit(struct gfs2_holder *gh); @@ -260,6 +261,11 @@ static inline bool gfs2_holder_initialized(struct gfs2_holder *gh) return gh->gh_gl; } +static inline bool gfs2_holder_queued(struct gfs2_holder *gh) +{ + return !list_empty(&gh->gh_list); +} + /** * glock_set_object - set the gl_object field of a glock * @gl: the glock diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h index 7a993d7c022e..6b450065b9d5 100644 --- a/fs/gfs2/incore.h +++ b/fs/gfs2/incore.h @@ -725,6 +725,7 @@ struct gfs2_sbd { struct gfs2_glock *sd_freeze_gl; struct work_struct sd_freeze_work; wait_queue_head_t sd_glock_wait; + wait_queue_head_t sd_async_glock_wait; atomic_t sd_glock_disposal; struct completion sd_locking_init; struct completion sd_wdack; diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 50eeb15c6f4f..e1e18fb587eb 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1388,16 +1388,18 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, } num_gh = 1; - gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, 0, ghs); + gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs); if (odip != ndip) { - gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE,GL_ASYNC, + ghs + num_gh); num_gh++; } - gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(ip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); num_gh++; if (nip) { - gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, + ghs + num_gh); num_gh++; } @@ -1406,6 +1408,9 @@ static int gfs2_rename(struct inode *odir, struct dentry *odentry, if (error) goto out_gunlock; } + error = gfs2_glock_async_wait(num_gh, ghs); + if (error) + goto out_gunlock; if (nip) { /* Grab the resource group glock for unlink flag twiddling. @@ -1555,7 +1560,8 @@ out_gunlock: gfs2_glock_dq_uninit(&rd_gh); while (x--) { - gfs2_glock_dq(ghs + x); + if (gfs2_holder_queued(ghs + x)) + gfs2_glock_dq(ghs + x); gfs2_holder_uninit(ghs + x); } out_gunlock_r: @@ -1585,7 +1591,7 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, 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; + struct gfs2_holder ghs[4], r_gh; unsigned int num_gh; unsigned int x; umode_t old_mode = oip->i_inode.i_mode; @@ -1619,15 +1625,16 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, } num_gh = 1; - gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, 0, ghs); + gfs2_holder_init(odip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs); if (odip != ndip) { - gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(ndip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, + ghs + num_gh); num_gh++; } - gfs2_holder_init(oip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(oip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); num_gh++; - gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, 0, ghs + num_gh); + gfs2_holder_init(nip->i_gl, LM_ST_EXCLUSIVE, GL_ASYNC, ghs + num_gh); num_gh++; for (x = 0; x < num_gh; x++) { @@ -1636,6 +1643,10 @@ static int gfs2_exchange(struct inode *odir, struct dentry *odentry, goto out_gunlock; } + error = gfs2_glock_async_wait(num_gh, ghs); + if (error) + goto out_gunlock; + error = -ENOENT; if (oip->i_inode.i_nlink == 0 || nip->i_inode.i_nlink == 0) goto out_gunlock; @@ -1696,7 +1707,8 @@ out_end_trans: gfs2_trans_end(sdp); out_gunlock: while (x--) { - gfs2_glock_dq(ghs + x); + if (gfs2_holder_queued(ghs + x)) + gfs2_glock_dq(ghs + x); gfs2_holder_uninit(ghs + x); } out_gunlock_r: diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c index 4a8e5a7310f0..f3fd5cd9d43f 100644 --- a/fs/gfs2/ops_fstype.c +++ b/fs/gfs2/ops_fstype.c @@ -87,6 +87,7 @@ static struct gfs2_sbd *init_sbd(struct super_block *sb) gfs2_tune_init(&sdp->sd_tune); init_waitqueue_head(&sdp->sd_glock_wait); + init_waitqueue_head(&sdp->sd_async_glock_wait); atomic_set(&sdp->sd_glock_disposal, 0); init_completion(&sdp->sd_locking_init); init_completion(&sdp->sd_wdack); From b473bc2dcd5ad7c487f252d9d2b74ace70308b1f Mon Sep 17 00:00:00 2001 From: Andreas Gruenbacher Date: Fri, 6 Sep 2019 11:02:38 +0100 Subject: [PATCH 14/15] gfs2: Improve mmap write vs. truncate consistency On filesystems with a block size smaller than PAGE_SIZE, page_mkwrite is called for each memory-mapped page before that page can be written to. When such a memory-mapped file is truncated down to size x which is not a multiple of the page size and then back to a larger size, the page straddling size x can end up with a partial block mapping. In that case, make sure to mark that page read-only so that page_mkwrite will be called before the page can be written to the next time. (There is no point in marking the page straddling size x read-only when truncating down as writing to memory beyond the end of the file will result in SIGBUS instead of growing the file.) Fixes xfstests generic/029, generic/030 on filesystems with a block size smaller than PAGE_SIZE. Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 2043a728f281..9ef543dd38e2 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -2139,7 +2139,7 @@ static int do_grow(struct inode *inode, u64 size) if (error) goto do_end_trans; - i_size_write(inode, size); + truncate_setsize(inode, size); ip->i_inode.i_mtime = ip->i_inode.i_ctime = current_time(&ip->i_inode); gfs2_trans_add_meta(ip->i_gl, dibh); gfs2_dinode_out(ip, dibh->b_data); From f0b444b349e33ae0d3dd93e25ca365482a5d17d4 Mon Sep 17 00:00:00 2001 From: Bob Peterson Date: Thu, 12 Sep 2019 13:54:27 -0400 Subject: [PATCH 15/15] gfs2: clear buf_in_tr when ending a transaction in sweep_bh_for_rgrps In function sweep_bh_for_rgrps, which is a helper for punch_hole, it uses variable buf_in_tr to keep track of when it needs to commit pending block frees on a partial delete that overflows the transaction created for the delete. The problem is that the variable was initialized at the start of function sweep_bh_for_rgrps but it was never cleared, even when starting a new transaction. This patch reinitializes the variable when the transaction is ended, so the next transaction starts out with it cleared. Fixes: d552a2b9b33e ("GFS2: Non-recursive delete") Cc: stable@vger.kernel.org # v4.12+ Signed-off-by: Bob Peterson Signed-off-by: Andreas Gruenbacher --- fs/gfs2/bmap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c index 9ef543dd38e2..f63df54a08c6 100644 --- a/fs/gfs2/bmap.c +++ b/fs/gfs2/bmap.c @@ -1632,6 +1632,7 @@ out_unlock: brelse(dibh); up_write(&ip->i_rw_mutex); gfs2_trans_end(sdp); + buf_in_tr = false; } gfs2_glock_dq_uninit(rd_gh); cond_resched();