Change set_page_dirty_buffers() and create_empty_buffers() to create dirty buffers only inside current i_size. With this patch, we can rely on buffer_dirty() to really mean "buffer has data that need to be written to disk" in __block_write_full_page(). With this we are able to distinguish cases a) block_commit_write() has marked buffers dirty but i_size is not yet updated and b) buffers are beyond i_size and were marked dirty by an accident. So we can write all dirty buffers in __block_write_full_page() and be sure that we have written all the dirty data a page carries (regardless whether the i_size update after a write already happened or not) and on the other hand don't call get_block() on buffers that are beyond end of file. Signed-off-by: Jan Kara <jack@xxxxxxx> --- fs/buffer.c | 38 +++++++++++++++++++++++--------------- 1 files changed, 23 insertions(+), 15 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 33da488..2b8cabe 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -710,13 +710,19 @@ int __set_page_dirty_buffers(struct page *page) spin_lock(&mapping->private_lock); if (page_has_buffers(page)) { + struct inode *inode = mapping->host; struct buffer_head *head = page_buffers(page); struct buffer_head *bh = head; + sector_t last_block = (i_size_read(inode) - 1) + >> inode->i_blkbits; + sector_t block = (sector_t)page->index << + (PAGE_CACHE_SHIFT - inode->i_blkbits); do { set_buffer_dirty(bh); bh = bh->b_this_page; - } while (bh != head); + block++; + } while (bh != head && block <= last_block); } newly_dirty = !TestSetPageDirty(page); spin_unlock(&mapping->private_lock); @@ -1527,7 +1533,9 @@ void create_empty_buffers(struct page *page, unsigned long blocksize, unsigned long b_state) { struct buffer_head *bh, *head, *tail; + int dirty = b_state & BH_Dirty; + b_state &= ~BH_Dirty; head = alloc_page_buffers(page, blocksize, 1); bh = head; do { @@ -1538,14 +1546,19 @@ void create_empty_buffers(struct page *page, tail->b_this_page = head; spin_lock(&page->mapping->private_lock); - if (PageUptodate(page) || PageDirty(page)) { + dirty |= PageDirty(page); + if (PageUptodate(page) || dirty) { + loff_t size = i_size_read(inode); + loff_t start = page_offset(page); + bh = head; do { - if (PageDirty(page)) + if (dirty && start < size) set_buffer_dirty(bh); if (PageUptodate(page)) set_buffer_uptodate(bh); bh = bh->b_this_page; + start += blocksize; } while (bh != head); } attach_page_buffers(page, head); @@ -1626,7 +1639,6 @@ static int __block_write_full_page(struct inode *inode, struct page *page, { int err; sector_t block; - sector_t last_block; struct buffer_head *bh, *head; const unsigned blocksize = 1 << inode->i_blkbits; int nr_underway = 0; @@ -1635,8 +1647,6 @@ static int __block_write_full_page(struct inode *inode, struct page *page, BUG_ON(!PageLocked(page)); - last_block = (i_size_read(inode) - 1) >> inode->i_blkbits; - if (!page_has_buffers(page)) { create_empty_buffers(page, blocksize, (1 << BH_Dirty)|(1 << BH_Uptodate)); @@ -1661,15 +1671,7 @@ static int __block_write_full_page(struct inode *inode, struct page *page, * handle any aliases from the underlying blockdev's mapping. */ do { - if (block > last_block) { - /* - * mapped buffers outside i_size will occur, because - * this page can be outside i_size when there is a - * truncate in progress. - */ - clear_buffer_dirty(bh); - set_buffer_uptodate(bh); - } else if ((!buffer_mapped(bh) || buffer_delay(bh)) && + if ((!buffer_mapped(bh) || buffer_delay(bh)) && buffer_dirty(bh)) { WARN_ON(bh->b_size != blocksize); err = get_block(inode, block, bh, 1); @@ -1703,6 +1705,12 @@ static int __block_write_full_page(struct inode *inode, struct page *page, redirty_page_for_writepage(wbc, page); continue; } + /* + * We write all mapped & dirty buffers. They can be beyond + * current i_size if there's a truncate in progress or if + * writepage() got called after block_commit_write() but before + * i_size was updated. + */ if (test_clear_buffer_dirty(bh)) { mark_buffer_async_write_endio(bh, handler); } else { -- 1.6.0.2 -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html