Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

fs: introduce some page/buffer invariants

It is a bug to set a page dirty if it is not uptodate unless it has
buffers. If the page has buffers, then the page may be dirty (some buffers
dirty) but not uptodate (some buffers not uptodate). The exception to this
rule is if the set_page_dirty caller is racing with truncate or invalidate.

A buffer can not be set dirty if it is not uptodate.

If either of these situations occurs, it indicates there could be some data
loss problem. Some of these warnings could be a harmless one where the
page or buffer is set uptodate immediately after it is dirtied, however we
should fix those up, and enforce this ordering.

Bring the order of operations for truncate into line with those of
invalidate. This will prevent a page from being able to go !uptodate while
we're holding the tree_lock, which is probably a good thing anyway.

Signed-off-by: Nick Piggin <npiggin@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Nick Piggin and committed by
Linus Torvalds
787d2214 a1ed3dda

+39 -18
+37 -17
fs/buffer.c
··· 676 676 EXPORT_SYMBOL(mark_buffer_dirty_inode); 677 677 678 678 /* 679 + * Mark the page dirty, and set it dirty in the radix tree, and mark the inode 680 + * dirty. 681 + * 682 + * If warn is true, then emit a warning if the page is not uptodate and has 683 + * not been truncated. 684 + */ 685 + static int __set_page_dirty(struct page *page, 686 + struct address_space *mapping, int warn) 687 + { 688 + if (unlikely(!mapping)) 689 + return !TestSetPageDirty(page); 690 + 691 + if (TestSetPageDirty(page)) 692 + return 0; 693 + 694 + write_lock_irq(&mapping->tree_lock); 695 + if (page->mapping) { /* Race with truncate? */ 696 + WARN_ON_ONCE(warn && !PageUptodate(page)); 697 + 698 + if (mapping_cap_account_dirty(mapping)) { 699 + __inc_zone_page_state(page, NR_FILE_DIRTY); 700 + task_io_account_write(PAGE_CACHE_SIZE); 701 + } 702 + radix_tree_tag_set(&mapping->page_tree, 703 + page_index(page), PAGECACHE_TAG_DIRTY); 704 + } 705 + write_unlock_irq(&mapping->tree_lock); 706 + __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); 707 + 708 + return 1; 709 + } 710 + 711 + /* 679 712 * Add a page to the dirty page list. 680 713 * 681 714 * It is a sad fact of life that this function is called from several places ··· 735 702 */ 736 703 int __set_page_dirty_buffers(struct page *page) 737 704 { 738 - struct address_space * const mapping = page_mapping(page); 705 + struct address_space *mapping = page_mapping(page); 739 706 740 707 if (unlikely(!mapping)) 741 708 return !TestSetPageDirty(page); ··· 752 719 } 753 720 spin_unlock(&mapping->private_lock); 754 721 755 - if (TestSetPageDirty(page)) 756 - return 0; 757 - 758 - write_lock_irq(&mapping->tree_lock); 759 - if (page->mapping) { /* Race with truncate? */ 760 - if (mapping_cap_account_dirty(mapping)) { 761 - __inc_zone_page_state(page, NR_FILE_DIRTY); 762 - task_io_account_write(PAGE_CACHE_SIZE); 763 - } 764 - radix_tree_tag_set(&mapping->page_tree, 765 - page_index(page), PAGECACHE_TAG_DIRTY); 766 - } 767 - write_unlock_irq(&mapping->tree_lock); 768 - __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); 769 - return 1; 722 + return __set_page_dirty(page, mapping, 1); 770 723 } 771 724 EXPORT_SYMBOL(__set_page_dirty_buffers); 772 725 ··· 1151 1132 */ 1152 1133 void fastcall mark_buffer_dirty(struct buffer_head *bh) 1153 1134 { 1135 + WARN_ON_ONCE(!buffer_uptodate(bh)); 1154 1136 if (!buffer_dirty(bh) && !test_set_buffer_dirty(bh)) 1155 - __set_page_dirty_nobuffers(bh->b_page); 1137 + __set_page_dirty(bh->b_page, page_mapping(bh->b_page), 0); 1156 1138 } 1157 1139 1158 1140 /*
+1
mm/page-writeback.c
··· 824 824 mapping2 = page_mapping(page); 825 825 if (mapping2) { /* Race with truncate? */ 826 826 BUG_ON(mapping2 != mapping); 827 + WARN_ON_ONCE(!PagePrivate(page) && !PageUptodate(page)); 827 828 if (mapping_cap_account_dirty(mapping)) { 828 829 __inc_zone_page_state(page, NR_FILE_DIRTY); 829 830 task_io_account_write(PAGE_CACHE_SIZE);
+1 -1
mm/truncate.c
··· 100 100 if (PagePrivate(page)) 101 101 do_invalidatepage(page, 0); 102 102 103 + remove_from_page_cache(page); 103 104 ClearPageUptodate(page); 104 105 ClearPageMappedToDisk(page); 105 - remove_from_page_cache(page); 106 106 page_cache_release(page); /* pagecache ref */ 107 107 } 108 108