[PATCH 3/5] vfs: Create dirty buffer only inside i_size

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux