aboutsummaryrefslogtreecommitdiffstats
path: root/fs/buffer.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2009-03-19 14:32:05 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-03-19 14:32:05 -0400
commita8e7d49aa7be728c4ae241a75a2a124cdcabc0c5 (patch)
treefec49351496b886d8aa0e7b55e58c2f1394b051a /fs/buffer.c
parent68df3755e383e6fecf2354a67b08f92f18536594 (diff)
Fix race in create_empty_buffers() vs __set_page_dirty_buffers()
Nick Piggin noticed this (very unlikely) race between setting a page dirty and creating the buffers for it - we need to hold the mapping private_lock until we've set the page dirty bit in order to make sure that create_empty_buffers() might not build up a set of buffers without the dirty bits set when the page is dirty. I doubt anybody has ever hit this race (and it didn't solve the issue Nick was looking at), but as Nick says: "Still, it does appear to solve a real race, which we should close." Acked-by: Nick Piggin <nickpiggin@yahoo.com.au> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/buffer.c')
-rw-r--r--fs/buffer.c23
1 files changed, 11 insertions, 12 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 9f697419ed8e..891e1c78e4f1 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -760,15 +760,9 @@ EXPORT_SYMBOL(mark_buffer_dirty_inode);
760 * If warn is true, then emit a warning if the page is not uptodate and has 760 * If warn is true, then emit a warning if the page is not uptodate and has
761 * not been truncated. 761 * not been truncated.
762 */ 762 */
763static int __set_page_dirty(struct page *page, 763static void __set_page_dirty(struct page *page,
764 struct address_space *mapping, int warn) 764 struct address_space *mapping, int warn)
765{ 765{
766 if (unlikely(!mapping))
767 return !TestSetPageDirty(page);
768
769 if (TestSetPageDirty(page))
770 return 0;
771
772 spin_lock_irq(&mapping->tree_lock); 766 spin_lock_irq(&mapping->tree_lock);
773 if (page->mapping) { /* Race with truncate? */ 767 if (page->mapping) { /* Race with truncate? */
774 WARN_ON_ONCE(warn && !PageUptodate(page)); 768 WARN_ON_ONCE(warn && !PageUptodate(page));
@@ -785,8 +779,6 @@ static int __set_page_dirty(struct page *page,
785 } 779 }
786 spin_unlock_irq(&mapping->tree_lock); 780 spin_unlock_irq(&mapping->tree_lock);
787 __mark_inode_dirty(mapping->host, I_DIRTY_PAGES); 781 __mark_inode_dirty(mapping->host, I_DIRTY_PAGES);
788
789 return 1;
790} 782}
791 783
792/* 784/*
@@ -816,6 +808,7 @@ static int __set_page_dirty(struct page *page,
816 */ 808 */
817int __set_page_dirty_buffers(struct page *page) 809int __set_page_dirty_buffers(struct page *page)
818{ 810{
811 int newly_dirty;
819 struct address_space *mapping = page_mapping(page); 812 struct address_space *mapping = page_mapping(page);
820 813
821 if (unlikely(!mapping)) 814 if (unlikely(!mapping))
@@ -831,9 +824,12 @@ int __set_page_dirty_buffers(struct page *page)
831 bh = bh->b_this_page; 824 bh = bh->b_this_page;
832 } while (bh != head); 825 } while (bh != head);
833 } 826 }
827 newly_dirty = !TestSetPageDirty(page);
834 spin_unlock(&mapping->private_lock); 828 spin_unlock(&mapping->private_lock);
835 829
836 return __set_page_dirty(page, mapping, 1); 830 if (newly_dirty)
831 __set_page_dirty(page, mapping, 1);
832 return newly_dirty;
837} 833}
838EXPORT_SYMBOL(__set_page_dirty_buffers); 834EXPORT_SYMBOL(__set_page_dirty_buffers);
839 835
@@ -1262,8 +1258,11 @@ void mark_buffer_dirty(struct buffer_head *bh)
1262 return; 1258 return;
1263 } 1259 }
1264 1260
1265 if (!test_set_buffer_dirty(bh)) 1261 if (!test_set_buffer_dirty(bh)) {
1266 __set_page_dirty(bh->b_page, page_mapping(bh->b_page), 0); 1262 struct page *page = bh->b_page;
1263 if (!TestSetPageDirty(page))
1264 __set_page_dirty(page, page_mapping(page), 0);
1265 }
1267} 1266}
1268 1267
1269/* 1268/*