aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteven Whitehouse <swhiteho@redhat.com>2006-08-31 12:14:44 -0400
committerSteven Whitehouse <swhiteho@redhat.com>2006-08-31 12:14:44 -0400
commit623d93555c8884768db65ffc11509c93e50dd4db (patch)
tree08be5484217e9c7851b129d2794b4afe8e44760c
parent899be4d3b7e00bf364d84c1c8cfe8bbbd1e3507b (diff)
[GFS2] Fix releasepage bug (fixes direct i/o writes)
This patch fixes three main bugs. Firstly the direct i/o get_block was returning the wrong return code in certain cases. Secondly, the GFS2's releasepage function was not dealing with cases when clean, ordered buffers were found still queued on a transaction (which can happen depending on the ordering of journal flushes). Thirdly, the journaling code itself needed altering to take account of the after effects of removing the clean ordered buffers from the transactions before a journal flush. The releasepage bug did also show up under "normal" buffered i/o as well, so its not just a fix for direct i/o. In fact its not normally used in the direct i/o path at all, except when flushing existing buffers after performing a direct i/o write, but that was the code path that led us to spot this. Signed-off-by: Steven Whitehouse <swhiteho@redhat.com>
-rw-r--r--fs/gfs2/lops.c11
-rw-r--r--fs/gfs2/ops_address.c33
2 files changed, 40 insertions, 4 deletions
diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c
index 08de8b7fb31..0ec38b39909 100644
--- a/fs/gfs2/lops.c
+++ b/fs/gfs2/lops.c
@@ -592,6 +592,17 @@ static void databuf_lo_before_commit(struct gfs2_sbd *sdp)
gfs2_log_lock(sdp);
if (n++ > num)
break;
+ } else if (!bd1->bd_bh) {
+ total_dbuf--;
+ sdp->sd_log_num_databuf--;
+ list_del_init(&bd1->bd_le.le_list);
+ if (bd1 == bd2) {
+ bd2 = NULL;
+ bd2 = list_prepare_entry(bd2,
+ &sdp->sd_log_le_databuf,
+ bd_le.le_list);
+ }
+ kmem_cache_free(gfs2_bufdata_cachep, bd1);
}
}
gfs2_log_unlock(sdp);
diff --git a/fs/gfs2/ops_address.c b/fs/gfs2/ops_address.c
index 48720421c79..0de7a952363 100644
--- a/fs/gfs2/ops_address.c
+++ b/fs/gfs2/ops_address.c
@@ -118,6 +118,25 @@ static int get_block_noalloc(struct inode *inode, sector_t lblock,
return error;
}
+static int get_block_direct(struct inode *inode, sector_t lblock,
+ struct buffer_head *bh_result, int create)
+{
+ int new = 0;
+ u64 dblock;
+ int error, boundary;
+
+ error = gfs2_block_map(inode, lblock, &new, &dblock, &boundary);
+ if (error)
+ return error;
+
+ if (dblock) {
+ map_bh(bh_result, inode->i_sb, dblock);
+ if (boundary)
+ set_buffer_boundary(bh_result);
+ }
+
+ return 0;
+}
/**
* gfs2_writepage - Write complete page
* @page: Page to write
@@ -661,7 +680,7 @@ static ssize_t gfs2_direct_IO(int rw, struct kiocb *iocb,
rv = blockdev_direct_IO_own_locking(rw, iocb, inode,
inode->i_sb->s_bdev,
iov, offset, nr_segs,
- gfs2_get_block, NULL);
+ get_block_direct, NULL);
out:
gfs2_glock_dq_m(1, &gh);
gfs2_holder_uninit(&gh);
@@ -724,7 +743,7 @@ static unsigned limit = 0;
}
/**
- * gfs2_aspace_releasepage - free the metadata associated with a page
+ * gfs2_releasepage - free the metadata associated with a page
* @page: the page that's being released
* @gfp_mask: passed from Linux VFS, ignored by us
*
@@ -761,16 +780,22 @@ int gfs2_releasepage(struct page *page, gfp_t gfp_mask)
}
gfs2_assert_warn(sdp, !buffer_pinned(bh));
+ gfs2_assert_warn(sdp, !buffer_dirty(bh));
+ gfs2_log_lock(sdp);
bd = bh->b_private;
if (bd) {
gfs2_assert_warn(sdp, bd->bd_bh == bh);
gfs2_assert_warn(sdp, list_empty(&bd->bd_list_tr));
- gfs2_assert_warn(sdp, list_empty(&bd->bd_le.le_list));
gfs2_assert_warn(sdp, !bd->bd_ail);
- kmem_cache_free(gfs2_bufdata_cachep, bd);
+ bd->bd_bh = NULL;
+ if (!list_empty(&bd->bd_le.le_list))
+ bd = NULL;
bh->b_private = NULL;
}
+ gfs2_log_unlock(sdp);
+ if (bd)
+ kmem_cache_free(gfs2_bufdata_cachep, bd);
bh = bh->b_this_page;
} while (bh != head);